前言
做java開發(fā)的,一般都避免不了要面對java線程池技術,像tomcat之類的容器天然就支持多線程。
即使是做偏后端技術,如處理一些消息,執(zhí)行一些計算任務,也經常需要用到線程池技術。
鑒于線程池技術的重要性,接下來會分多篇介紹java中提供的ThreadPoolExecutor線程池實現(xiàn)的底層機制。只有對機制了然于胸,才能更好駕馭這把利器。
線程池技術演示流程
關鍵概念:?
如果說怎么最容易了解線程池的實現(xiàn)原理,那就是一步一步動態(tài)的演示。為了便于理解,這里先介紹幾個線程池用到的概念。
ThreadPoolExecutor:?這是線程池實現(xiàn)類,會動態(tài)創(chuàng)建多個線程,并發(fā)執(zhí)行提交的多個任務;
Worker:?是個Runnable實現(xiàn)類來的,內部會創(chuàng)建一個線程,一直循環(huán)不斷執(zhí)行任務,所以可以認為一個Worker就是一個工作線程;
corePoolSize:?當池中總線程數(shù)
到了corePoolSize,那么池中總線程數(shù)是不會跌破到corePoolSize以下的。(除非 allowCoreThreadTimeOut=true并且keepAliveTime>0);
maximumPoolSize:?當池中線程數(shù)達到了corePoolSize,這時候新提交的任務就會放入等待隊列中,一般情況下,這些任務會被前面創(chuàng)建的 corePoolSize個線程執(zhí)行。當任務提交速度過快,隊列滿了,這時候,如果當前總線程數(shù)
keepAliveTime:存活時間,分兩種情況:
(1)allowCoreThreadTimeOut=true,所有線程,一旦創(chuàng)建后,在keepAliveTime時間內,如果沒有任務可以執(zhí)行,則該線程會退出并銷毀,這樣的好處是系統(tǒng)不忙時可以回收線程資源;(2)allowCoreThreadTimeOut=false,如果總線程數(shù)<=corePoolSize,那么這些線程是不會退出的,他們會一直不斷的等待任務并執(zhí)行,哪怕當前沒有任務,
但如果線程數(shù)>corePoolSize,而且一旦一個線程閑的時間超過 keepAliveTime則會退出,但一旦降低到corePoolSize,則不會再退出了。?
allowCoreThreadTimeOut:?用于決定是否在系統(tǒng)閑時可以逐步回收所有的線程,如果為allowCoreThreadTimeOut=true,必須結合keepAliveTime一起使用,用于決定當線程數(shù)
workQueue:這是一個阻塞隊列,當線程數(shù)>=corePoolSize,這時候提交的任務將會放入阻塞隊列中,如果阻塞隊列是無界的,那么總的線程數(shù)是不可能>corePoolSize的,即maximumPoolSize屬性就是無用的;如果阻塞隊列是有界的,而且未滿,則任務入隊,否則根據maximumPoolSize的值判斷是要新建線程執(zhí)行新任務或者是根據策略丟棄任務。
有了以上的概念,接下來將根據 allowCoreThreadTimeOut的值分兩種場景進行演示說明。
演示一: allowCoreThreadTimeOut=true
1、 初值設定: corePoolSize=2; maximumPoolSize=3;keepAliveTime=10s; workQueue容量為2; 初始狀態(tài)圖如下所示:

2、submit一個任務A,由于總線程數(shù)0<corePoolSize; 此時會創(chuàng)建一個線程執(zhí)行任務A,狀態(tài)圖如下:

3、submit一個任務B,由于總線程數(shù)1<corePoolSize,此時會創(chuàng)建一個線程執(zhí)行任務B,狀態(tài)圖如下:

4、submit一個任務C,由于總線程數(shù) 2=corePoolSize,workQueue不滿,這時候任務C入隊列,狀態(tài)圖如下:

5、submit一個任務D,很明顯,任務D入隊列,狀態(tài)圖如下:

6、submit一個任務E,這時候,線程數(shù)2=corePoolSize,workQueue也已經滿了,判斷發(fā)現(xiàn)線程數(shù)2<maximumPoolSize,所以繼續(xù)創(chuàng)建線程執(zhí)行任務E,狀態(tài)圖如下:

7、submit一個任務F,這時候,2=corePoolSize,workQueue已滿,判斷發(fā)現(xiàn)線程數(shù)3=maximumPoolSize,這種情況下,
線程池會根據策略來決定是否要放棄當前任務,或者是把workQueue中一個任務刪除,然后入隊新的任務,也可以自定義策略,
比如,持久化到DB之類的,或者是發(fā)出警報。我們假設是直接丟棄策略,這時候狀態(tài)圖不變。
8、這會沒有新任務到來了,各個任務陸續(xù)執(zhí)行完了,包括隊列中的C和D也執(zhí)行完了,這時候,由于當前場景為allowCoreThreadTimeOut=true,
如果在等待keepAliveTime時間后線程仍舊無法獲取新的任務,線程將會自行退出,這將導致最終所有線程都退出了,也就是又再次回到了原始狀態(tài),如下圖所示:

說得更簡單一些就是:在 allowCoreThreadTimeOut=true時,如果一個線程等了keepAliveTime還無法獲取新任務,則退出。
演示二:allowCoreThreadTimeOut=false
1~7 的步驟與狀態(tài)跟“演示一”是一樣的,所以這里不再贅述,這時候狀態(tài)圖如下:

8、這會沒有新任務到來了,各個任務陸續(xù)執(zhí)行完了,包括隊列中的C和D也執(zhí)行完了,這時候,由于當前場景為allowCoreThreadTimeOut=false,并且線程數(shù)3>corePoolSize,這時候每個線程都感知到線程數(shù)過多,所以它們都會嘗試把自己停止掉,實現(xiàn)中最終只會停止一個線程,剩余線程數(shù)2=corePoolSize,接下來,哪怕一直沒有新任務來,這corePoolSize個線程也不會退出,一直存活著等待接收任務。這時候,狀態(tài)圖如下:

線程池的狀態(tài)
前一部分演示了線程池的基本實現(xiàn)原理,這一小節(jié)介紹一下線程池的狀態(tài),線程池概括上講有5種狀態(tài),如下圖所示:

RUNNING狀態(tài):?
創(chuàng)建線程池的時候,線程池的初始狀態(tài)為 RUNNING,接著就可以提交任務執(zhí)行了。
SHUTDOWN狀態(tài):?
當在RUNNING狀態(tài)調用shutdown()時,線程池狀態(tài)會被改為SHUTDOWN,這時候,submit任務的時候,會被拒絕,可以使用多種拒絕策略,?
比如最簡單就是直接丟棄任務。至于正在執(zhí)行中的線程,會繼續(xù)執(zhí)行,同時會把阻塞隊列中的任務也一并執(zhí)行完畢,等到全部任務執(zhí)行完畢,線程池會進入 TIDYING狀態(tài),等執(zhí)行鉤子方法terminated()之后,就會進入最終狀態(tài)TERMINATED,這時候,整個線程池完全終止。
STOP狀態(tài):?
當在RUNNING狀態(tài)調用shutdownNow()時,線程池狀態(tài)會被改為STOP,這時候,submit任務會被拒絕,那么如果有任務執(zhí)行到一半,該怎么處理?其實,執(zhí)行shutdownNow()時,會中斷各個工作線程,所以任務會如何執(zhí)行要看任務做的是什么事情,有沒有處理中斷異常。而阻塞隊列如何有任務,這些任務將不會再執(zhí)行,shutdownNow()執(zhí)行后,將會返回阻塞隊列中的未執(zhí)行的任務列表。
TIDYING狀態(tài):?
TIDYING只是一個過渡狀態(tài),當所有工作線程都停止后,線程池的狀態(tài)會進入TIDYING,然后執(zhí)行一個鉤子方法terminated(),最后線程池會進入TERMINATED狀態(tài)。
TERMINATED狀態(tài):?
線程池終止狀態(tài),這個沒什么可說的了,大家都明白。
總結
理解線程池最主要是要理解線程池幾個主要的配置參數(shù),如果不看實現(xiàn)細節(jié),原理還是比較簡單的。在了解原理的基礎上再去看代碼,就會事半功倍。