1. 程式人生 > >Java基礎——多執行緒

Java基礎——多執行緒

Java基礎-多執行緒

多個執行緒一起做同一件事情,縮短時間,提升效率
提高資源利用率
加快程式響應,提升使用者體驗

建立執行緒

1. 繼承Thread類

  • 步驟
    • 繼承Thread類,重寫run方法
    • 呼叫的時候,直接new一個物件,然後調start()方法啟動執行緒
  • 特點
    • 由於是繼承方式,所以不建議使用,因為Java是單繼承的,不夠靈活
    • Thread類本質也是實現Runnable介面(public class Thread implements Runnable)

  

2. 實現Runnable介面

  • 步驟
    • 實現Runnable介面,重寫run()方法
    • 建立Runnable實現類的例項,並用這個例項作為Thread的target來建立Thread物件
    • 呼叫Thread類例項物件的start()方法啟動執行緒
  • 特點
    • 只是實現,保留了繼承的其他類的能力
    • 如果需要訪問當前執行緒,必須使用Thread.currentThread()方法

  

3. 實現 Callable介面

  • 步驟
    • 實現Callable介面,重寫call()方法。
    • 建立Callable實現類的例項,使用FutureTask類來包裝Callable物件
    • 並用FutureTask例項作為Thread的target來建立Thread物件
    • 呼叫Thread類例項物件的start()方法啟動執行緒
    • 呼叫FutureTask類例項物件的get()方法獲取非同步返回值
  • 特點
    • call方法可以丟擲異常
    • 只是實現,保留了繼承的其他類的能力
    • 如果需要訪問當前執行緒,必須使用Thread.currentThread()方法
    • 通過FutureTask物件可以瞭解任務執行情況,可取消任務的執行,還可獲取執行結果

  

4. 匿名內部類實現

  • 說明
    • 本質還是前面的方法,只是使用了匿名內部類來實現,簡化程式碼
    • Callable介面之所以把FutureTask類的例項化寫出來,是因為需要通過task物件獲取返回值

  

引數傳遞

1. 通過構造方法傳遞資料

  通過前面的學習,可以看到,不管何種建立物件的方式,都需要新建立例項,所以我們可以通過建構函式傳入引數,並將傳入的資料使用類變數儲存起來

  • 特點
    • 線上程執行之前,資料就已經傳入了
    • 使用構造引數,當引數較多時,使用不方便
    • 不同引數條件需要不同的構造方法,使得構造方法較多

2. 通過變數和方法傳遞資料

  線上程類裡面定義一些列的變數,然後定義set方法,在新建例項之後,呼叫set方法傳遞引數

  • 特點
    • 在引數較多時使用方便,按需傳遞引數

3. 通過回撥函式傳遞資料

  使用執行緒方法自己產生的變數值作為引數,去調取外部的方法,獲取返回資料的方式

  • 特點
    • 擁有獲取資料的主動權
執行緒同步

  要跨執行緒維護正確的可見性,只要在幾個執行緒之間共享非 final 變數,就必須使用執行緒同步

1. ThreadLocal

  ThreadLocal利用空間換時間,通過為每個執行緒提供一個獨立的變數副本,避免了資源等待,解決了變數併發訪問的衝突問題,提高了併發量。實現了執行緒間的資料隔離,但是執行緒間無法共享同一個資源

public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SyncTest syncTest = new SyncTest();
        ConcurrentHashMap<String, String> testConMap = new ConcurrentHashMap<>();
        for (int i = 0; i < 10; i++) {
            ThreadTest2 threadTest2 = new ThreadTest2();
            threadTest2.setSyncTest(syncTest);
            Thread threadTest = new Thread(threadTest2);
            threadTest.start();
        }
    }
}

//實現Runnable
class ThreadTest2 implements Runnable {
    private SyncTest syncTest;

    public void setSyncTest(SyncTest syncTest) {
        this.syncTest = syncTest;
    }

    @Override
    public void run() {
        syncTest.threadLocalTest(Thread.currentThread().getName());
    }
}

class SyncTest {
    private static ThreadLocal<String> threadLocal = new ThreadLocal<String>();

    public void threadLocalTest(String name) {
        try {
            System.out.println(name + "進入了threadLocal方法!");
            threadLocal.set(name);
            Thread.currentThread().sleep(100);
            System.out.println(threadLocal.get() + "離開了threadLocal方法!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

2. synchronized

  不管synchronized是用來修飾方法,還是修飾程式碼塊,其本質都是鎖定某一個物件。修飾方法時,鎖上的是呼叫這個方法的物件,即this;修飾程式碼塊時,鎖上的是括號裡的那個物件

  • 特點
    • 鎖的物件越小越好
public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        SyncTest syncTest = new SyncTest();
        ConcurrentHashMap<String, String> testConMap = new ConcurrentHashMap<>();
        for (int i = 0; i < 10; i++) {
            ThreadTest2 threadTest2 = new ThreadTest2();
            threadTest2.setSyncTest(syncTest);
            threadTest2.setTestConMap(testConMap);
            Thread threadTest = new Thread(threadTest2);
            threadTest.start();
        }
    }

}
//實現Runnable
class ThreadTest2 implements Runnable {
    private ConcurrentHashMap<String, String> testConMap;
    private SyncTest syncTest;

    public void setTestConMap(ConcurrentHashMap<String, String> testConMap) {
        this.testConMap = testConMap;
    }

    public void setSyncTest(SyncTest syncTest) {
        this.syncTest = syncTest;
    }

    @Override
    public void run() {
        //三個方法需要單獨測試,因為testConMap會相互影響

        //測試同步方法,鎖住的物件是syncTest
        //syncTest.testSyncMethod(testConMap,Thread.currentThread().getName());
        //測試同步程式碼塊,鎖住的物件是testConMap
        //syncTest.testSyncObject(testConMap, Thread.currentThread().getName());
        //測試沒有鎖時執行請求是多麼的混亂!!!
        //syncTest.testNoneSyncObject(testConMap, Thread.currentThread().getName());
    }
}
//同步測試方法類
class SyncTest {
    public synchronized void testSyncMethod(ConcurrentHashMap<String, String> testConMap, String name) {
        try {
            System.out.println(name + "進入了同步方法!");
            testConMap.put("name", name);
            Thread.currentThread().sleep(10);
            System.out.println(testConMap.get("name") + "離開了同步方法!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public void testSyncObject(ConcurrentHashMap<String, String> testConMap, String name) {
        synchronized (testConMap) {
            try {
                System.out.println(name + "進入了同步程式碼塊!");
                testConMap.put("name", name);
                Thread.currentThread().sleep(10);
                System.out.println(testConMap.get("name") + "離開了同步程式碼塊!");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

    public void testNoneSyncObject(ConcurrentHashMap<String, String> testConMap, String name) {
        try {
            System.out.println(name + "進入了無人管轄區域!");
            testConMap.put("name", name);
            Thread.currentThread().sleep(10);
            System.out.println(testConMap.get("name") + "離開了無人管轄區域!");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

3. volatile

  • 特點

    • 保證可見性,有序性,不保證原子性
    • 它會強制將對快取的修改操作立即寫入主存
    • volatile不適合複合操作(對變數的寫操作不依賴於當前值),否則需要保證只有單一執行緒能夠修改變數的值
    • 使用volatile關鍵字,可以禁止指令重排序(單例雙重檢查鎖)
public class StudyThread {
    static int v = 1;//volatile能夠保證變數的可見性

    public static void main(String[] args) {

        //改動執行緒
        new Thread(new Runnable() {
            public void run() {
                for (int i = 0; i < 10; i++) {
                    v++;//確保只有一個執行緒修改變數值
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
        //檢測執行緒
        new Thread(new Runnable() {
            @Override
            public void run() {
                int old = 0;
                while (old < 11) {
                    if (old != v) {
                        old = v;
                        System.out.println("檢測執行緒:v的值變動為" + old);
                    }
                }
            }
        }).start();
    }
}

4. 執行緒安全的類

  Java中很多類說的執行緒安全指的是,它的每個方法單獨呼叫(即原子操作)都是執行緒安全的,但是程式碼總體的互斥性並不受控制

  • 執行緒安全的類有以下幾類

    • Concurrentxxx

    • ThreadPoolExecutor

    • BlockingQueue和BlockingDeque

    • 原子類Atomicxxx—包裝類的執行緒安全類

    • CopyOnWriteArrayList和CopyOnWriteArraySet

    • 通過synchronized 關鍵字給方法加上內建鎖來實現執行緒安全:Timer,TimerTask,Vector,Stack,HashTable,StringBuffer

    • Collections中的synchronizedCollection(Collection c)方法可將一個集合變為執行緒安全:

      Map m=Collections.synchronizedMap(new HashMap());

執行緒池

執行緒池只能放入實現Runable或callable類執行緒,不能直接放入繼承Thread的類

1. Executors執行緒池的實現

  • 要點
    • 可能導致資源耗盡,OOM問題出現
    • 執行緒池不允許使用Executors去建立,而是通過ThreadPoolExecutor的方式(阿里巴巴java開發)
public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        //建立一個執行緒池,該執行緒池重用固定數量的從共享無界佇列中執行的執行緒
        //ExecutorService threadPool = Executors.newFixedThreadPool(20);
        //建立一個維護足夠的執行緒以支援給定的並行級別的執行緒池,執行緒的實際數量可以動態增長和收縮,工作竊取池不保證執行提交的任務的順序
        //ExecutorService threadPool = Executors.newWorkStealingPool(8);
        //建立一個使用從無界佇列執行的單個工作執行緒的執行程式。
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();
        //建立一個根據需要建立新執行緒的執行緒池,但在可用時將重新使用以前構造的執行緒。如果沒有可用的執行緒,將建立一個新的執行緒並將其新增到該池中。未使用六十秒的執行緒將被終止並從快取中刪除
        ExecutorService threadPool = Executors.newCachedThreadPool();
        //放入Runnable類執行緒
        for (int i = 0; i < 10; i++) {
            threadPool.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("執行緒名:" + Thread.currentThread().getName());
                }
            });
        }
        //Thread.currentThread().sleep(1000);
        //放入Callable類執行緒
        List<Future<String>> futures = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<String> future = threadPool.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("執行緒名:" + Thread.currentThread().getName());
                    return Thread.currentThread().getName();
                }
            });
            futures.add(future);
        }
        threadPool.shutdown();
        for (Future future : futures) {
            System.out.println(future.get());
        }
    }
}

1. ThreadPoolExecutor建立執行緒池

  

  • 要點

    • 執行緒池空閒大小和最大執行緒數根據實際情況確定
    • keepAliveTime一般設定為0
    • unit一般設定為TimeUnit.SECONDS(其他的也行,反正是0)
    • 任務佇列需要指定大小,不要使用無界佇列,容易造成OOM-> new ArrayBlockingQueue<>(512)
    • ThreadFactory threadFactory使用系統預設的
    • 拒絕策略:
      • AbortPolicy:丟擲RejectedExecutionException(該異常是非受檢異常,要記得捕獲)
      • DiscardPolicy:什麼也不做,直接忽略
      • DiscardOldestPolicy:丟棄執行佇列中最老的任務,嘗試為當前提交的任務騰出位置
      • CallerRunsPolicy:直接由提交任務者執行這個任務
public class StudyThread {

    public static void main(String[] args) throws ExecutionException, InterruptedException {
        int poolSize = Runtime.getRuntime().availableProcessors() * 2;
        BlockingQueue<Runnable> queue = new ArrayBlockingQueue<>(512);
        RejectedExecutionHandler policy = new ThreadPoolExecutor.DiscardPolicy();
        ExecutorService executorService = new ThreadPoolExecutor(poolSize, poolSize,
                0, TimeUnit.SECONDS,
                queue,
                policy);
        //放入Runnable類執行緒
        for (int i = 0; i < 10; i++) {
            executorService.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("執行緒名:" + Thread.currentThread().getName());
                }
            });
        }

        //放入Callable類執行緒
        List<Future<String>> futures = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            Future<String> future = executorService.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("執行緒名:" + Thread.currentThread().getName());
                    return Thread.currentThread().getName();
                }
            });
            futures.add(future);
        }
        for (Future future:futures) {
            System.out.println(future.get());
        }

        //放入Callable類執行緒
        //使用CompletionService簡化獲取結果的操作,執行完一個任務,獲取一個結果,結果順序和執行順序相同
        CompletionService<String> ecs = new ExecutorCompletionService<String>(executorService);
        for (int i = 0; i < 10; i++) {
            Future<String> future = ecs.submit(new Callable<String>() {
                @Override
                public String call() throws Exception {
                    System.out.println("執行緒名:" + Thread.currentThread().getName());
                    return Thread.currentThread().getName();
                }
            });
        }
        for (int i = 0; i < 10; i++) {
            System.out.println(ecs.take().get());
        }
    }
}

相關推薦

Java基礎執行執行安全-同步鎖三種形式

首先,我們通過一個案例,演示執行緒的安全問題: 電影院要賣票,我們模擬電影院的賣票過程。假設要播放的電影是 “葫蘆娃大戰奧特曼”,本次電影的座位共100個(本場電影只能賣100張票)。我們來模擬電影院的售票視窗,實現多個視窗同時賣 “終結者”這場電影票(多個視窗一起賣這100張票)需要視窗

java基礎執行(4)—執行可見性volatile 執行封閉threadlocal CAS操作

一。執行緒的可見性volatile(不是太常用,因為他只能解決執行緒可見和阻止指令排序,並不能解決多執行緒的併發問題) volatile:(1)保證變數的修改讓所有執行緒可見 (2)阻止指令排序 這個程式有3個結果,0,42,沒有進入迴圈直接結束

java基礎---------執行與反射

第一部分多執行緒: java語言是為數不多的支援多執行緒的語言。 程序:當程式進入記憶體執行時,即變成了一個程序。程序是處於執行過程中的程式,並且具有一定的獨立功能,程序是系統進行資源分配和排程的一個獨立單位。 硬碟和記憶體都可以儲存資料。硬碟是持久儲存裝置,記憶體是臨時

java基礎--執行

      java多執行緒的應用非常廣泛,主要是為了發揮伺服器多處理器的效率。在我們的web程式設計中應用非常廣泛。允許多使用者併發同時訪問,同時下載多個圖片等等均是應用了多執行緒。但在程式設計的時候好像關於多執行緒的程式碼感覺不到是因為我們將多執行緒繼承到框架裡面了,S

Java基礎-執行

class Res { String name; String sex; Boolean flag =false; // private String name; // private String sex; // private Boolean flag =false; // public sync

Java基礎——執行

Java基礎-多執行緒 多個執行緒一起做同一件事情,縮短時間,提升效率 提高資源利用率 加快程式響應,提升使用者體驗 建立執行緒 1. 繼承Thread類 步驟 繼承Thread類,重寫run方法 呼叫的時候,直接new一個物件,然後調start()方法啟動執行緒 特點 由於是繼承方式,所以不建

Java執行基礎

Java中的多執行緒 程序:一個程式的執行 執行緒:程式中某個順序的流程控制,通俗來講,就是不同的任務 一個程序可以有多個執行緒 執行緒的分類 普通執行緒(非Daemon執行緒) 在Java中,如果還有非Daemon執行緒,整個程式就不會結束 Daemon

Java這些執行基礎知識你會嗎?

0、併發和並行、程序核線程、多程序和多執行緒的區別: (這裡的時間和時刻上的概念同物理上的一樣) 併發:在一段時間內多個任務同時執行,或者說是在一段很短的時間內可以執行多條程式指令,微觀上看起來好像是可以同時執行多個程序,單核處理器就可以做到。 並行:在同一時刻多個任務

java se基礎-執行

多執行緒1.理解程式、程序、執行緒的概念         程式可以理解為靜態的程式碼         程序可以理解為執行中的程式         執行緒可以理解為程序的進一步細分,程式的一條執行路徑2.如何建立java程式的執行緒(重點)方式一:繼承Thread類class

java基礎-JAVA執行

------<a href="http://www.itheima.com" target="blank">Java培訓、Android培訓、iOS培訓、.Net培訓</a>、期待與您交流! ------- 要想了解執行緒,先了解程序的概念程序: 程

Java執行基礎

執行緒的狀態 狀態名稱 說明 new 初始狀態:執行緒被構建,但沒有呼叫start()方法 runnable 執行狀態:就緒和執行統稱“執行中” blocked 阻塞狀態:執行緒阻塞於鎖 waitin

JAVA夯實基礎——執行

多執行緒概述         程序:             正在執行的程式,是系統進行資源分配和呼叫的獨立單位。             每一個程序都有它自己的記憶體空間和系統資源。         執行緒(Thread):            是程序中的單個順

Java執行Java基礎複習歸納系列)

目錄 1.程序 2.執行緒 一、執行緒概述 1.程序 在一個作業系統中,每個獨立執行的程式都可稱之為一個程序,也就是“正在執行的程式”。 程序和程式 A  proce

Java 建立執行

1、繼承java.lang.Thread方式 執行start方法:MyThread的run就會被執行 程式碼片段: import java.util.Scanner; public class Main { public static void main(String[] a

java執行系列翻譯之java併發/執行教程

原文地址:http://tutorials.jenkov.com/java-concurrency/index.html 以前計算機都是單核,同時只能執行一個程式。之後出現了多重任務處理,這意味著計算機同時可以處理多個程式(又名任務或流程)。但這不是真正的“同時執行”,只是單個CPU被多個程式共

java隨筆——執行

//多執行緒 //第一種方法 package we; public class me { public static void main(String[] args) { My p=new My(); p.start();//啟用多執行緒 while(true)

【學習筆記】Java-Concurrent-執行容器

BlockingQueue 阻塞佇列 高頻函式:   boolean put() 新增一個元素 沒有空間則一直阻塞等待   boolean add() 新增一個元素 沒有空間則丟擲IllegalStateException異常   boolean off

【學習筆記】Java-Concurrent-執行測試模板

import java.util.concurrent.CountDownLatch; /** * 多執行緒測試模板 * * @author Mairuis * @date 2018/10/11 */ public class ConcurrentTest { public s

java執行一定快嗎?看完就知道!!!

理解上下文切換   即使是單核處理器也支援多執行緒執行程式碼,CPU通過每個執行緒分配CPU時間片來實現這個機制.時間片是CPU分配給多個執行緒的時間,因為時間片非常短,所以CPU通過不停的切換執行緒執行,讓我們感覺多個執行緒是同時執行的,時間片一般是幾十毫秒(ms).  

java初學執行

一、實現多執行緒的兩種方式   1.繼承Tread類   2.實現Runnable介面   3.匿名內部類 二、具體實現   1.繼承Tread類    1 public class Demo2_Thread { 2 public static void main(String