5種方法,教你判斷線程池是否全部完成
最近寫小玩具的時候用到了 CountDownLatch 計數(shù)器,然后順便想了想判斷線程池全部結(jié)束有多少種方法。
在網(wǎng)上搜了下,可能有些沒找到,但是我找到的有(所有方法都是在 ThreadPoolExecutor 線程池方法下測試的):
isTerminated()判斷方式,在執(zhí)行shutdown(),關(guān)閉線程池后,判斷是否所有任務(wù)已經(jīng)完成。ThreadPoolExecutor的getCompletedTaskCount()方法,判斷完成任務(wù)數(shù)和全部任務(wù)數(shù)是否相等。CountDownLatch計數(shù)器,使用閉鎖計數(shù)來判斷是否全部完成。手動維護(hù)一個公共計數(shù) ,原理和閉鎖類似,就是更加靈活。 使用 submit向線程池提交任務(wù),Future判斷任務(wù)執(zhí)行狀態(tài)。
好嘞,現(xiàn)在開始一個一個介紹優(yōu)缺點(diǎn)和簡要原理;
先創(chuàng)建一個 static 線程池,后面好幾個例子就不一一創(chuàng)建了,全部用這個就行了:
/**
?*?創(chuàng)建一個最大線程數(shù)是20的線程池
?*/
public?static?ThreadPoolExecutor?pool?=?new?ThreadPoolExecutor(
?????10,?20,?0L,
?????TimeUnit.MILLISECONDS,
?????new?LinkedBlockingQueue<>());
然后再準(zhǔn)備一個通用的睡眠方法:
/**
?*?線程執(zhí)行方法,隨機(jī)等待0到10秒
?*/
private?static?void?sleepMtehod(int?index){
????try?{
????????long?sleepTime?=?new?Double(Math.random()?*?10000).longValue();
????????Thread.sleep(sleepTime);
????????System.out.println("當(dāng)前線程執(zhí)行結(jié)束:?"?+?index);
????}?catch?(InterruptedException?e)?{
????????e.printStackTrace();
????}
}
這個方法就是為了測試的時候區(qū)分線程執(zhí)行完畢的下順序而已。
好嘞,準(zhǔn)備完畢,現(xiàn)在開始。
isTerminated 方式
首先貼上測試代碼:
private?static?void?shutdownTest()?throws?Exception?{
????for?(int?i?=?0;?i?30;?i++)?{
????????int?index?=?i;
????????pool.execute(()?->?sleepMtehod(index));
????}
????pool.shutdown();
????while?(!pool.isTerminated()){
????????Thread.sleep(1000);
????????System.out.println("還沒停止。。。");
????}
????System.out.println("全部執(zhí)行完畢");
}
這一種方式就是在主線程中進(jìn)行循環(huán)判斷,全部任務(wù)是否已經(jīng)完成。
這里有兩個主要方法:
shutdown():啟動有序關(guān)閉,其中先前提交的任務(wù)將被執(zhí)行,但不會接受任何新任務(wù)。如果已經(jīng)關(guān)閉,調(diào)用沒有額外的作用。isTerminated():如果所有任務(wù)在關(guān)閉后完成,則返回true。請注意,isTerminated從不是 true,除非shutdown或shutdownNow先被執(zhí)行。
通俗點(diǎn)講,就是在執(zhí)行全部任務(wù)后,對線程池進(jìn)行 shutdown() 有序關(guān)閉,然后循環(huán)判斷 isTerminated() ,線程池是否全部完成。
優(yōu)點(diǎn) :操作簡單,代碼更加簡單。 缺點(diǎn) :需要關(guān)閉線程池。一般我在代碼中都是將線程池注入到Spring容器,然后各個組件中統(tǒng)一用同一個,當(dāng)然不能關(guān)閉。
類似方法擴(kuò)展:
shutdownNow():嘗試停止所有主動執(zhí)行的任務(wù),停止等待任務(wù)的處理,并返回正在等待執(zhí)行的任務(wù)列表。從此方法返回時,這些任務(wù)將從任務(wù)隊列中刪除。通過Thread.interrupt()取消任務(wù)。isShutdown():如果線程池已關(guān)閉,則返回 true 。isTerminating():如果在shutdown()或shutdownNow()之后終止 ,但尚未完全終止,則返回true。waitTermination(long timeout, TimeUnit unit):當(dāng)前線程阻塞,直到等所有已提交的任務(wù)(包括正在跑的和隊列中等待的)執(zhí)行完,或者等超時時間到,或者線程被中斷拋出異常;全部執(zhí)行完返回true,超時返回false。也可以用這個方法代替isTerminated()進(jìn)行判斷 。
getCompletedTaskCount
還是一樣,貼上代碼:
private?static?void?taskCountTest()?throws?Exception?{
????????for?(int?i?=?0;?i?30;?i++)?{
????????????int?index?=?i;
????????????pool.execute(()?->?sleepMtehod(index));
????????}
????????//當(dāng)線程池完成的線程數(shù)等于線程池中的總線程數(shù)
????????while?(!(pool.getTaskCount()?==?pool.getCompletedTaskCount()))?{
????????????System.out.println("任務(wù)總數(shù):"?+?pool.getTaskCount()?+?";?已經(jīng)完成任務(wù)數(shù):"?+?pool.getCompletedTaskCount());
????????????Thread.sleep(1000);
????????????System.out.println("還沒停止。。。");
????????}
????????System.out.println("全部執(zhí)行完畢");
????}
還是一樣在主線程循環(huán)判斷,主要就兩個方法:
getTaskCount():返回計劃執(zhí)行的任務(wù)總數(shù)。由于任務(wù)和線程的狀態(tài)可能在計算過程中動態(tài)變化,因此返回的值只是一個近似值。getCompletedTaskCount():返回完成執(zhí)行的任務(wù)的大致總數(shù)。因?yàn)槿蝿?wù)和線程的狀態(tài)可能在計算過程中動態(tài)地改變,所以返回的值只是一個近似值,但是在連續(xù)的調(diào)用中并不會減少。
這個好理解,總?cè)蝿?wù)數(shù)等于已完成任務(wù)數(shù),就表示全部執(zhí)行完畢。
優(yōu)點(diǎn) :完全使用了 ThreadPoolExecutor提供的方法,并且不必關(guān)閉線程池,避免了創(chuàng)建和銷毀帶來的損耗。缺點(diǎn) :上面的解釋也看到了,使用這種判斷存在很大的限制條件;必須確定,在循環(huán)判斷過程中,沒有新的任務(wù)產(chǎn)生。差不多意思就是,這個線程池只能在這條線程中使用。
其他 :
最后扯兩句,因?yàn)槲矣肕ain方法行的,跑完后 main 沒有結(jié)束,是因?yàn)榉鞘刈o(hù)線程如果不終止,程序是不會結(jié)束的。而線程池 Worker 線程里寫了一個死循環(huán),而且被設(shè)置成了非守護(hù)線程。
CountDownLatch 計數(shù)器
這種方法是我比較常用的方法,先看代碼:
private?static?void?countDownLatchTest()?throws?Exception?{
?????//計數(shù)器,判斷線程是否執(zhí)行結(jié)束
?????CountDownLatch?taskLatch?=?new?CountDownLatch(30);
?????for?(int?i?=?0;?i?30;?i++)?{
?????????int?index?=?i;
?????????pool.execute(()?->?{
?????????????sleepMtehod(index);
?????????????taskLatch.countDown();
?????????????System.out.println("當(dāng)前計數(shù)器數(shù)量:"?+?taskLatch.getCount());
?????????});
?????}
?????//當(dāng)前線程阻塞,等待計數(shù)器置為0
?????taskLatch.await();
?????System.out.println("全部執(zhí)行完畢");
?}
這種方法,呃,應(yīng)該是看起來比較高級的,我也不知道別的大佬怎么寫的,反正我就用這個。
這個方法需要介紹下這個工具類 CountDownLatch 。先把這種方式的優(yōu)缺點(diǎn)寫了,后面再詳細(xì)介紹這個類。
優(yōu)點(diǎn) :代碼優(yōu)雅,不需要對線程池進(jìn)行操作,將線程池作為 Bean 的情況下有很好的使用場景。 缺點(diǎn) :需要提前知道線程數(shù)量;性能確實(shí),呃呃呃呃呃,差了點(diǎn)。哦對了,還需要在線程代碼塊內(nèi)加上異常判斷,否則在 countDown之前發(fā)生異常而沒有處理,就會導(dǎo)致主線程永遠(yuǎn)阻塞在 await。
CountDownLatch 概述
CountDownLatch 是 JDK 提供的一個同步工具,它可以讓一個或多個線程等待,一直等到其他線程中執(zhí)行完成一組操作。
常用的方法有 countDown 方法和 await 方法,CountDownLatch 在初始化時,需要指定用給定一個整數(shù)作為計數(shù)器。
當(dāng)調(diào)用 countDown 方法時,計數(shù)器會被減1;當(dāng)調(diào)用 await 方法時,如果計數(shù)器大于0時,線程會被阻塞,一直到計數(shù)器被 countDown 方法減到0時,線程才會繼續(xù)執(zhí)行。
計數(shù)器是無法重置的,當(dāng)計數(shù)器被減到0時,調(diào)用 await 方法都會直接返回。
維護(hù)一個公共計數(shù)
這種方式其實(shí)和 CountDownLatch 原理類似。
先維護(hù)一個靜態(tài)變量
private?static?int?taskNum?=?0;
然后在線程任務(wù)結(jié)束時,進(jìn)行靜態(tài)變量操作:
private?static?void?staticCountTest()?throws?Exception?{
?????Lock?lock?=?new?ReentrantLock();
?????for?(int?i?=?0;?i?30;?i++)?{
?????????int?index?=?i;
?????????pool.execute(()?->?{
?????????????sleepMtehod(index);
?????????????lock.lock();
?????????????taskNum++;
?????????????lock.unlock();
?????????});
?????}
?????while(taskNum?30)?{
?????????Thread.sleep(1000);
?????????System.out.println("還沒停止。。。當(dāng)前完成任務(wù)數(shù):"?+?taskNum);
?????}
?????System.out.println("全部執(zhí)行完畢");
?}
其實(shí)就是加鎖計數(shù),循環(huán)判斷。
優(yōu)點(diǎn) :手動維護(hù)方式更加靈活,對于一些特殊場景可以手動處理。 缺點(diǎn) :和 CountDownLatch相比,一樣需要知道線程數(shù)目,但是代碼實(shí)現(xiàn)比較麻煩,相對于靈活這一個優(yōu)勢,貌似投入產(chǎn)出并不對等。
Future 判斷任務(wù)執(zhí)行狀態(tài)
Future 是用來裝載線程結(jié)果的,不過,用這個來進(jìn)行判斷寫代碼總感覺怪怪的。
因?yàn)?Future 只能裝載一條線程的返回結(jié)果,多條線程總不能用 List 在接收 Future 。
這里就開一個線程做個演示:
private?static?void?futureTest()?throws?Exception?{
????Future>?future?=?pool.submit(()?->?sleepMtehod(1));
????while?(!future.isDone()){
????????Thread.sleep(500);
????????System.out.println("還沒停止。。。");
????}
????System.out.println("全部執(zhí)行完畢");
}
這種方式就不寫優(yōu)缺點(diǎn)了,因?yàn)?Future 的主要使用場景并不是用于判斷任務(wù)執(zhí)行狀態(tài)。
來源:https://blog.csdn.net/m0_46144826
如有文章對你有幫助,
“在看”和轉(zhuǎn)發(fā)是對我最大的支持!
推薦:
點(diǎn)擊領(lǐng)取:151個大廠面試講解!(圖片可上下滑動!)??


