1. 程式人生 > 其它 >Java多執行緒 以7種方式讓主執行緒等待子執行緒結束

Java多執行緒 以7種方式讓主執行緒等待子執行緒結束

技術標籤:java

個人站:https://www.yuansu.space

一、while迴圈

對於“主執行緒如何獲取子執行緒總執行時間”的問題,最開始想到的是使用while迴圈進行輪詢:

Thread t = new Thread(() -> {
    //子執行緒進行字串連線操作
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over"
); }); //開始計時 long start = System.currentTimeMillis(); System.out.println("start = " + start); t.start(); long end = 0; while(t.isAlive() == true){//t.getState() != State.TERMINATED這兩種判斷方式都可以 end = System.currentTimeMillis(); } System.out.println("end = " + end); System.out.println
("end - start = " + (end - start));

但是這樣太消耗CPU,所以我在while迴圈里加入了暫停:

while(t.isAlive() == true){
    end = System.currentTimeMillis();
    try {
        Thread.sleep(10);
    }catch (InterruptedException e){
        e.printStackTrace();
    }
}

二、Thread的join()方法

接著我又找到了第二種方法:

long start = System.
currentTimeMillis(); System.out.println("start = " + start); t1.start(); try { t.join();//注意這裡 } catch (InterruptedException e) { e.printStackTrace(); } long end = System.currentTimeMillis(); System.out.println("end = " + end); System.out.println("end - Start:" + (end - start));

使用join()方法,join()方法的作用,是等待這個執行緒結束;(t.join()方法阻塞呼叫此方法的執行緒(calling thread),直到執行緒t完成,此執行緒再繼續)

三、synchronized的等待喚醒機制

Object lock = new Object();
Thread t = new Thread(() -> {    
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
    lock.notify();//子執行緒喚醒
});
//計時
long start = System.currentTimeMillis();
System.out.println("start = " + start);
//啟動子執行緒
t.start();
try {
    lock.wait();//主執行緒等待
} catch (InterruptedException e) {
    e.printStackTrace();
}

long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));

但是這樣會丟擲兩個異常:
在這裡插入圖片描述
由於對wait()和notify()的理解並不是很深刻,所以我最開始並不清楚為什麼會出現這樣的結果,因為從報錯順序來看子執行緒並沒有提前喚醒,於是我在segmentfault和CSDN都發出了提問,同時也詢問了我一個很厲害的朋友,最後得出的結論是呼叫wait()方法時需要獲取該物件的鎖,Object文件裡是這麼說的:

The current thread must own this object’s monitor.
IllegalMonitorStateException - if the current thread is not the owner of the object’s monitor.

所以上面的程式碼需要改成這樣:

Thread t = new Thread(() -> {
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
    synchronized (lock) {//獲取物件鎖
        lock.notify();//子執行緒喚醒
    }
});
//計時
long start = System.currentTimeMillis();
System.out.println("start = " + start);
//啟動子執行緒
t.start();
try {
    synchronized (lock) {//這裡也是一樣
        lock.wait();//主執行緒等待
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));

這樣的確得出了結果,但是我想知道兩個執行緒的執行順序,於是在wait和nitify前後分別加了一個輸出,最後得出的執行結果是:
在這裡插入圖片描述
可以看出主執行緒先wait子執行緒再notify,也就是說,如果子執行緒在主執行緒wati前呼叫了nitify,會導致主執行緒無限等待,所以這個思路還是有一定的漏洞的。

四、CountDownLatch

第四種方式可以等待多個執行緒結束,就是使用java.util.concurrent包下的CountDownLatch類
簡單來說,CountDownLatch類是一個計數器,可以設定初始執行緒數(設定後不能改變),在子執行緒結束時呼叫countDown()方法可以使執行緒數減一,最終為0的時候,呼叫CountDownLatch的成員方法wait()的執行緒就會取消BLOKED阻塞狀態,進入RUNNABLE從而繼續執行。下面上程式碼:

int threadNumber = 1;
final CountDownLatch cdl = new CountDownLatch(threadNumber);//引數為執行緒個數

Thread t = new Thread(() -> {
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
    cdl.countDown();//此方法是CountDownLatch的執行緒數-1
});

long start = System.currentTimeMillis();
System.out.println("start = " + start);
t.start();
//執行緒啟動後呼叫countDownLatch方法
try {
    cdl.await();//需要捕獲異常,當其中執行緒數為0時這裡才會繼續執行
}catch (InterruptedException e){
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));

五、Future

ExecutorService executorService = Executors.newFixedThreadPool(1);

Thread t = new Thread(() -> {
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
});
long start = System.currentTimeMillis();
System.out.println("start = " + start);
Future future = executorService.submit(t);//子執行緒啟動
try {
    future.get();//需要捕獲兩種異常
}catch (InterruptedException e){
    e.printStackTrace();
}catch (ExecutionException e){
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));
executorService.shutdown();

這裡, ThreadPoolExecutor 是實現了 ExecutorService的方法,
sumbit的過程就是把一個Runnable介面物件包裝成一個 Callable介面物件, 然後放到 workQueue裡等待排程執行.
當然, 執行的啟動也是呼叫了thread的start來做到的, 只不過這裡被包裝掉了. 另外, 這裡的thread是會被重複利用的,
所以這裡要退出主執行緒, 需要執行以下shutdown方法以示退出使用執行緒池. 扯遠了.
這種方法是得益於Callable介面和Future模式, 呼叫future介面的get方法, 會同步等待該future執行結束,
然後獲取到結果. Callbale介面的介面方法是 V call(); 是可以有返回結果的, 而Runnable的 void run(),
是沒有返回結果的. 所以, 這裡即使被包裝成Callbale介面, future.get返回的結果也是null的.如果需要得到返回結果,
建議使用Callable介面.

六、BlockingQueue

同時,在concurrent包中,還提供了BlockingQueue(佇列)來操作執行緒,BlockingQueue的主要的用法是線上程間安全有效的傳遞資料,具體用法可以參見這篇部落格,對於BlockingQueue說的非常詳細。因此,第六種方法也出來了:

BlockingQueue queue = new ArrayBlockingQueue(1);//陣列型佇列,長度為1
Thread t = new Thread(() -> {
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
    try {
        queue.put("OK");//在佇列中加入資料
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
long start = System.currentTimeMillis();
System.out.println("start = " + start);
t.start();
try {
    queue.take();//主執行緒在佇列中獲取資料,take()方法會阻塞佇列,ps還有不會阻塞的方法
} catch (InterruptedException e) {
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));

七、CyclicBarrier

那麼,有沒有第七種方式呢?當然有啦~,還是concurrent包,只不過這次試用CyclicBarrier類:

CyclicBarrier字面意思迴環柵欄,通過它可以實現讓一組執行緒等待至某個狀態之後再全部同時執行。叫做迴環是因為當所有等待執行緒都被釋放以後,CyclicBarrier可以被重用。

CyclicBarrier barrier = new CyclicBarrier(2);//引數為執行緒數
Thread t = new Thread(() -> {
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
    try {
        barrier.await();//阻塞
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
});
long start = System.currentTimeMillis();
System.out.println("start = " + start);
t.start();
try {
    barrier.await();//也阻塞,並且當阻塞數量達到指定數目時同時釋放
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (BrokenBarrierException e) {
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));

小結
while迴圈進行輪詢
Thread類的join方法
synchronized鎖
CountDownLatch
Future
BlockingQueue
CyclicBarrier