Java多執行緒 以7種方式讓主執行緒等待子執行緒結束
技術標籤:java
一、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