如何判断线程池已经执行完所有任务了?
作者 | 磊哥
來(lái)源 | Java面試真題解析(ID:aimianshi666)
轉(zhuǎn)載請(qǐng)聯(lián)系授權(quán)(微信ID:GG_Stone)
很多場(chǎng)景下,我們需要等待線程池的所有任務(wù)都執(zhí)行完,然后再進(jìn)行下一步操作。對(duì)于線程 Thread 來(lái)說(shuō),很好實(shí)現(xiàn),加一個(gè) join 方法就解決了,然而對(duì)于線程池的判斷就比較麻煩了。
我們本文提供 4 種判斷線程池任務(wù)是否執(zhí)行完的方法:
使用 isTerminated 方法判斷。
使用 getCompletedTaskCount 方法判斷。
使用 CountDownLatch 判斷。
使用 CyclicBarrier 判斷。
接下來(lái)我們一個(gè)一個(gè)來(lái)看。
不判斷的問(wèn)題
如果不對(duì)線程池是否已經(jīng)執(zhí)行完做判斷,就會(huì)出現(xiàn)以下問(wèn)題,如下代碼所示:
import?java.util.Random; import?java.util.concurrent.LinkedBlockingDeque; import?java.util.concurrent.ThreadPoolExecutor; import?java.util.concurrent.TimeUnit;public?class?ThreadPoolCompleted?{public?static?void?main(String[]?args)?{//?創(chuàng)建線程池ThreadPoolExecutor?threadPool?=?new?ThreadPoolExecutor(10,?20,0,?TimeUnit.SECONDS,?new?LinkedBlockingDeque<>(1024));//?添加任務(wù)addTask(threadPool);//?打印結(jié)果System.out.println("線程池任務(wù)執(zhí)行完成!");}/***?給線程池添加任務(wù)*/private?static?void?addTask(ThreadPoolExecutor?threadPool)?{//?任務(wù)總數(shù)final?int?taskCount?=?5;//?添加任務(wù)for?(int?i?=?0;?i?<?taskCount;?i++)?{final?int?finalI?=?i;threadPool.submit(new?Runnable()?{@Overridepublic?void?run()?{try?{//?隨機(jī)休眠?0-4sint?sleepTime?=?new?Random().nextInt(5);TimeUnit.SECONDS.sleep(sleepTime);}?catch?(InterruptedException?e)?{e.printStackTrace();}System.out.println(String.format("任務(wù)%d執(zhí)行完成",?finalI));}});}} }以上程序的執(zhí)行結(jié)果如下:從上述執(zhí)行結(jié)果可以看出,程序先打印了“線程池任務(wù)執(zhí)行完成!”,然后還在陸續(xù)的執(zhí)行線程池的任務(wù),這種執(zhí)行順序混亂的結(jié)果,并不是我們期望的結(jié)果。我們想要的結(jié)果是等所有任務(wù)都執(zhí)行完之后,再打印“線程池任務(wù)執(zhí)行完成!”的信息。
產(chǎn)生以上問(wèn)題的原因是因?yàn)橹骶€程 main,和線程池是并發(fā)執(zhí)行的,所以當(dāng)線程池還沒(méi)執(zhí)行完,main 線程的打印結(jié)果代碼就已經(jīng)執(zhí)行了。想要解決這個(gè)問(wèn)題,就需要在打印結(jié)果之前,先判斷線程池的任務(wù)是否已經(jīng)全部執(zhí)行完,如果沒(méi)有執(zhí)行完就等待任務(wù)執(zhí)行完再執(zhí)行打印結(jié)果。
方法1:isTerminated
我們可以利用線程池的終止?fàn)顟B(tài)(TERMINATED)來(lái)判斷線程池的任務(wù)是否已經(jīng)全部執(zhí)行完,但想要線程池的狀態(tài)發(fā)生改變,我們就需要調(diào)用線程池的 shutdown 方法,不然線程池一直會(huì)處于 RUNNING 運(yùn)行狀態(tài),那就沒(méi)辦法使用終止?fàn)顟B(tài)來(lái)判斷任務(wù)是否已經(jīng)全部執(zhí)行完了,它的實(shí)現(xiàn)代碼如下:
import?java.util.Random; import?java.util.concurrent.LinkedBlockingDeque; import?java.util.concurrent.ThreadPoolExecutor; import?java.util.concurrent.TimeUnit;/***?線程池任務(wù)執(zhí)行完成判斷*/ public?class?ThreadPoolCompleted?{public?static?void?main(String[]?args)?{//?1.創(chuàng)建線程池ThreadPoolExecutor?threadPool?=?new?ThreadPoolExecutor(10,?20,0,?TimeUnit.SECONDS,?new?LinkedBlockingDeque<>(1024));//?2.添加任務(wù)addTask(threadPool);//?3.判斷線程池是否執(zhí)行完isCompleted(threadPool);?//?【核心調(diào)用方法】//?4.線程池執(zhí)行完System.out.println();System.out.println("線程池任務(wù)執(zhí)行完成!");}/***?方法1:isTerminated 實(shí)現(xiàn)方式*?判斷線程池的所有任務(wù)是否執(zhí)行完*/private?static?void?isCompleted(ThreadPoolExecutor?threadPool)?{threadPool.shutdown();while?(!threadPool.isTerminated())?{?//?如果沒(méi)有執(zhí)行完就一直循環(huán)}}/***?給線程池添加任務(wù)*/private?static?void?addTask(ThreadPoolExecutor?threadPool)?{//?任務(wù)總數(shù)final?int?taskCount?=?5;//?添加任務(wù)for?(int?i?=?0;?i?<?taskCount;?i++)?{final?int?finalI?=?i;threadPool.submit(new?Runnable()?{@Overridepublic?void?run()?{try?{//?隨機(jī)休眠?0-4sint?sleepTime?=?new?Random().nextInt(5);TimeUnit.SECONDS.sleep(sleepTime);}?catch?(InterruptedException?e)?{e.printStackTrace();}System.out.println(String.format("任務(wù)%d執(zhí)行完成",?finalI));}});}} }方法說(shuō)明:shutdown 方法是啟動(dòng)線程池有序關(guān)閉的方法,它在完全關(guān)閉之前會(huì)執(zhí)行完之前所有已經(jīng)提交的任務(wù),并且不會(huì)再接受任何新任務(wù)。當(dāng)線程池中的所有任務(wù)都執(zhí)行完之后,線程池就進(jìn)入了終止?fàn)顟B(tài),調(diào)用 isTerminated 方法返回的結(jié)果就是 true 了。
以上程序的執(zhí)行結(jié)果如下:
缺點(diǎn)分析
需要關(guān)閉線程池。
擴(kuò)展:線程池的所有狀態(tài)
線程池總共包含以下 5 種狀態(tài):
RUNNING:運(yùn)行狀態(tài)。
SHUTDOWN:關(guān)閉狀態(tài)。
STOP:阻斷狀態(tài)。
TIDYING:整理狀態(tài)。
TERMINATED:終止?fàn)顟B(tài)。
如果不調(diào)用線程池的關(guān)閉方法,那么線程池會(huì)一直處于 RUNNING 運(yùn)行狀態(tài)。
方法2:getCompletedTaskCount
我們可以通過(guò)判斷線程池中的計(jì)劃執(zhí)行任務(wù)數(shù)和已完成任務(wù)數(shù),來(lái)判斷線程池是否已經(jīng)全部執(zhí)行完,如果計(jì)劃執(zhí)行任務(wù)數(shù)=已完成任務(wù)數(shù),那么線程池的任務(wù)就全部執(zhí)行完了,否則就未執(zhí)行完,具體實(shí)現(xiàn)代碼如下:
/***?方法2:getCompletedTaskCount 實(shí)現(xiàn)方式*?判斷線程池的所有任務(wù)是否執(zhí)行完*/ private?static?void?isCompletedByTaskCount(ThreadPoolExecutor?threadPool)?{while?(threadPool.getTaskCount()?!=?threadPool.getCompletedTaskCount())?{} }以上程序執(zhí)行結(jié)果如下:
方法說(shuō)明
getTaskCount():返回計(jì)劃執(zhí)行的任務(wù)總數(shù)。由于任務(wù)和線程的狀態(tài)可能在計(jì)算過(guò)程中動(dòng)態(tài)變化,因此返回的值只是一個(gè)近似值。
getCompletedTaskCount():返回完成執(zhí)行任務(wù)的總數(shù)。因?yàn)槿蝿?wù)和線程的狀態(tài)可能在計(jì)算過(guò)程中動(dòng)態(tài)地改變,所以返回的值只是一個(gè)近似值,但是在連續(xù)的調(diào)用中并不會(huì)減少。
優(yōu)缺點(diǎn)分析
此實(shí)現(xiàn)方法的優(yōu)點(diǎn)是無(wú)需關(guān)閉線程池。它的缺點(diǎn)是 getTaskCount() 和 getCompletedTaskCount() 返回的是一個(gè)近似值,因?yàn)榫€程池中的任務(wù)和線程的狀態(tài)可能在計(jì)算過(guò)程中動(dòng)態(tài)變化,所以它們兩個(gè)返回的都是一個(gè)近似值。
方法3:CountDownLatch
CountDownLatch 可以理解為一個(gè)計(jì)數(shù)器,我們創(chuàng)建了一個(gè)包含 N 個(gè)任務(wù)的計(jì)數(shù)器,每個(gè)任務(wù)執(zhí)行完計(jì)數(shù)器 -1,直到計(jì)數(shù)器減為 0 時(shí),說(shuō)明所有的任務(wù)都執(zhí)行完了,就可以執(zhí)行下一段業(yè)務(wù)的代碼了,它的實(shí)現(xiàn)流程如下圖所示:具體實(shí)現(xiàn)代碼如下:
public?static?void?main(String[]?args)?throws?InterruptedException?{//?創(chuàng)建線程池ThreadPoolExecutor?threadPool?=?new?ThreadPoolExecutor(10,?20,0,?TimeUnit.SECONDS,?new?LinkedBlockingDeque<>(1024));final?int?taskCount?=?5;????//?任務(wù)總數(shù)//?單次計(jì)數(shù)器CountDownLatch?countDownLatch?=?new?CountDownLatch(taskCount);?//?①//?添加任務(wù)for?(int?i?=?0;?i?<?taskCount;?i++)?{final?int?finalI?=?i;threadPool.submit(new?Runnable()?{@Overridepublic?void?run()?{try?{//?隨機(jī)休眠?0-4sint?sleepTime?=?new?Random().nextInt(5);TimeUnit.SECONDS.sleep(sleepTime);}?catch?(InterruptedException?e)?{e.printStackTrace();}System.out.println(String.format("任務(wù)%d執(zhí)行完成",?finalI));//?線程執(zhí)行完,計(jì)數(shù)器?-1countDownLatch.countDown();??//?②}});}//?阻塞等待線程池任務(wù)執(zhí)行完countDownLatch.await();??//?③//?線程池執(zhí)行完System.out.println();System.out.println("線程池任務(wù)執(zhí)行完成!"); }代碼說(shuō)明:以上代碼中標(biāo)識(shí)為 ①、②、③ 的代碼行是核心實(shí)現(xiàn)代碼,其中:① 是聲明一個(gè)包含了 5 個(gè)任務(wù)的計(jì)數(shù)器;② 是每個(gè)任務(wù)執(zhí)行完之后計(jì)數(shù)器 -1;③ 是阻塞等待計(jì)數(shù)器 CountDownLatch 減為 0,表示任務(wù)都執(zhí)行完了,可以執(zhí)行 await 方法后面的業(yè)務(wù)代碼了。
以上程序的執(zhí)行結(jié)果如下:
優(yōu)缺點(diǎn)分析
CountDownLatch 寫(xiě)法很優(yōu)雅,且無(wú)需關(guān)閉線程池,但它的缺點(diǎn)是只能使用一次,CountDownLatch 創(chuàng)建之后不能被重復(fù)使用,也就是說(shuō) CountDownLatch 可以理解為只能使用一次的計(jì)數(shù)器。
方法4:CyclicBarrier
CyclicBarrier 和 CountDownLatch 類似,它可以理解為一個(gè)可以重復(fù)使用的循環(huán)計(jì)數(shù)器,CyclicBarrier 可以調(diào)用 reset 方法將自己重置到初始狀態(tài),CyclicBarrier 具體實(shí)現(xiàn)代碼如下:
public?static?void?main(String[]?args)?throws?InterruptedException?{//?創(chuàng)建線程池ThreadPoolExecutor?threadPool?=?new?ThreadPoolExecutor(10,?20,0,?TimeUnit.SECONDS,?new?LinkedBlockingDeque<>(1024));final?int?taskCount?=?5;????//?任務(wù)總數(shù)//?循環(huán)計(jì)數(shù)器?①CyclicBarrier?cyclicBarrier?=?new?CyclicBarrier(taskCount,?new?Runnable()?{@Overridepublic?void?run()?{//?線程池執(zhí)行完System.out.println();System.out.println("線程池所有任務(wù)已執(zhí)行完!");}});//?添加任務(wù)for?(int?i?=?0;?i?<?taskCount;?i++)?{final?int?finalI?=?i;threadPool.submit(new?Runnable()?{@Overridepublic?void?run()?{try?{//?隨機(jī)休眠?0-4sint?sleepTime?=?new?Random().nextInt(5);TimeUnit.SECONDS.sleep(sleepTime);System.out.println(String.format("任務(wù)%d執(zhí)行完成",?finalI));//?線程執(zhí)行完cyclicBarrier.await();?//?②}?catch?(InterruptedException?e)?{e.printStackTrace();}?catch?(BrokenBarrierException?e)?{e.printStackTrace();}}});} }以上程序的執(zhí)行結(jié)果如下:
方法說(shuō)明
CyclicBarrier 有 3 個(gè)重要的方法:
構(gòu)造方法:構(gòu)造方法可以傳遞兩個(gè)參數(shù),參數(shù) 1 是計(jì)數(shù)器的數(shù)量 parties,參數(shù) 2 是計(jì)數(shù)器為 0 時(shí),也就是任務(wù)都執(zhí)行完之后可以執(zhí)行的事件(方法)。
await 方法:在 CyclicBarrier 上進(jìn)行阻塞等待,當(dāng)調(diào)用此方法時(shí) CyclicBarrier ?的內(nèi)部計(jì)數(shù)器會(huì) -1,直到發(fā)生以下情形之一:
在 CyclicBarrier 上等待的線程數(shù)量達(dá)到 parties,也就是計(jì)數(shù)器的聲明數(shù)量時(shí),則所有線程被釋放,繼續(xù)執(zhí)行。
當(dāng)前線程被中斷,則拋出 InterruptedException 異常,并停止等待,繼續(xù)執(zhí)行。
其他等待的線程被中斷,則當(dāng)前線程拋出 BrokenBarrierException 異常,并停止等待,繼續(xù)執(zhí)行。
其他等待的線程超時(shí),則當(dāng)前線程拋出 BrokenBarrierException 異常,并停止等待,繼續(xù)執(zhí)行。
其他線程調(diào)用 CyclicBarrier.reset() 方法,則當(dāng)前線程拋出 BrokenBarrierException 異常,并停止等待,繼續(xù)執(zhí)行。
reset 方法:使得CyclicBarrier回歸初始狀態(tài),直觀來(lái)看它做了兩件事:
如果有正在等待的線程,則會(huì)拋出 BrokenBarrierException 異常,且這些線程停止等待,繼續(xù)執(zhí)行。
將是否破損標(biāo)志位 broken 置為 false。
優(yōu)缺點(diǎn)分析
CyclicBarrier 從設(shè)計(jì)的復(fù)雜度到使用的復(fù)雜度都高于 CountDownLatch,相比于 CountDownLatch 來(lái)說(shuō)它的優(yōu)點(diǎn)是可以重復(fù)使用(只需調(diào)用 reset 就能恢復(fù)到初始狀態(tài)),缺點(diǎn)是使用難度較高。
總結(jié)
我們本文提供 4 種判斷線程池任務(wù)是否執(zhí)行完的方法:
使用 ?isTerminated 方法判斷:通過(guò)判斷線程池的完成狀態(tài)來(lái)實(shí)現(xiàn),需要關(guān)閉線程池,一般情況下不建議使用。
使用 getCompletedTaskCount 方法判斷:通過(guò)計(jì)劃執(zhí)行總?cè)蝿?wù)量和已經(jīng)完成總?cè)蝿?wù)量,來(lái)判斷線程池的任務(wù)是否已經(jīng)全部執(zhí)行,如果相等則判定為全部執(zhí)行完成。但因?yàn)榫€程個(gè)體和狀態(tài)都會(huì)發(fā)生改變,所以得到的是一個(gè)大致的值,可能不準(zhǔn)確。
使用 CountDownLatch 判斷:相當(dāng)于一個(gè)線程安全的單次計(jì)數(shù)器,使用比較簡(jiǎn)單,且不需要關(guān)閉線程池,是比較常用的判斷方法。
使用 CyclicBarrier 判斷:相當(dāng)于一個(gè)線程安全的重復(fù)計(jì)數(shù)器,但使用較為復(fù)雜,所以日常項(xiàng)目中使用的較少。
是非審之于己,毀譽(yù)聽(tīng)之于人,得失安之于數(shù)。
公眾號(hào):Java面試真題解析
面試合集:https://gitee.com/mydb/interview
往期推薦面試突擊34:如何使用線程池執(zhí)行定時(shí)任務(wù)?
面試突擊33:線程池有哪些狀態(tài)?狀態(tài)是如何轉(zhuǎn)換的?
面試突擊32:為什么創(chuàng)建線程池一定要用ThreadPoolExecutor?
總結(jié)
以上是生活随笔為你收集整理的如何判断线程池已经执行完所有任务了?的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。
- 上一篇: 为什么wait和notify必须放在sy
- 下一篇: 厉害了,自己手写一个Java热加载!