千鋒扣丁學(xué)堂Java培訓(xùn)之線程池ThreadPoolExecutor實(shí)現(xiàn)原理詳解
2019-07-26 14:02:10
4438瀏覽
今天千鋒扣丁學(xué)堂
Java培訓(xùn)老師給大家分享一篇關(guān)于線程池ThreadPoolExecutor實(shí)現(xiàn)原理詳解,首先將分析Java線程池ThreadPoolExecutor的實(shí)現(xiàn)原理,掌握了實(shí)現(xiàn)原理能幫助你更好的優(yōu)化程序的性能,避免很多錯(cuò)誤用法。本文的代碼量較少,主要講原理,等你把原理完全弄明白了代碼隨便一寫一大堆。
1.線程池的狀態(tài)
首先,線程池是一個(gè)有狀態(tài)的對(duì)象。狀態(tài)有以下幾種:
·RUNNING:運(yùn)行中。此時(shí)線程池能接受任務(wù),并且會(huì)處理隊(duì)列中的任務(wù);
·SHUTDOWN:關(guān)閉中。此時(shí),線程池不接受新任務(wù),但是會(huì)處理隊(duì)列中的任務(wù);
·STOP:停止。此時(shí)線程池不接受新任務(wù),也不會(huì)處理隊(duì)列中的任務(wù),還會(huì)中斷worker線程。
·TIDYING:清理中。所有任務(wù)都已終止且線程數(shù)等于0,開始調(diào)用terminated()。
·TERMINATED:終止。terminated()執(zhí)行結(jié)束。
線程池的狀態(tài)變化方式如下圖所示。
2.線程池的內(nèi)部組件
線程池的內(nèi)部主要包含如下表所示組件。知道ThreadPoolExecutor內(nèi)部包含哪些對(duì)象,基本就清楚線程池的實(shí)現(xiàn)原理了。注意,表中未給出largestPoolSize、completedTaskCount、keepAliveTime、corePoolSize和maximumPoolSize等參數(shù)。
3.線程池提交任務(wù)執(zhí)行邏輯
之前的文章介紹過(guò)線程池中線程數(shù)和阻塞隊(duì)列的變化關(guān)系。一般就是優(yōu)先創(chuàng)建核心線程,然后存放在任務(wù)隊(duì)列,再然后就是創(chuàng)建更多線程,如下圖所示。
實(shí)際上,線程池ThreadPoolExecutor的提交方法,即execute(),就是按照這三個(gè)階段執(zhí)行的。提交分3步:首先,判斷線程數(shù)是否小于核心線程數(shù),如果小于,則立即創(chuàng)建一個(gè)worker并且直接把任務(wù)傳入新創(chuàng)建的worker(任務(wù)不會(huì)進(jìn)入阻塞隊(duì)列),立即啟動(dòng)worker線程,函數(shù)返回;然后,若線程數(shù)大于核心線程數(shù),則嘗試把任務(wù)放入阻塞隊(duì)列,如果成功則需要再判斷一下線程池的狀態(tài)是否為SHUTDOWN,并且也要判斷一下worker線程數(shù)是否又變成0。如果變成SHUTDOWN,則需要把剛剛放入隊(duì)列的任務(wù)拿出隊(duì)列(因?yàn)橥蝗话l(fā)生的shutdown()調(diào)用),然后執(zhí)行按照飽和策略處理(給handler)。如果線程數(shù)變成0,則需要重新再創(chuàng)建一個(gè)worker線程(剛才檢查過(guò)的核心線程正好掛掉,如果不創(chuàng)建worker則線程池此時(shí)進(jìn)入靜止?fàn)顟B(tài));最后,如果放入阻塞隊(duì)列失敗,則創(chuàng)建非核心worker線程處理該任務(wù)。如果這個(gè)操作也失敗,則把任務(wù)交給飽和策略(handler)處理。流程圖如下:
注意:在每?jī)蓚€(gè)操作之間都要重新判斷線程池的狀態(tài)和線程數(shù)(中間有其他操作),這樣才能保證ThreadPoolExecutor的線程安全性。由此可見,設(shè)計(jì)線程安全的類是很難的。
4.Worker線程的執(zhí)行邏輯
線程池使用worker線程從隊(duì)列取任務(wù)執(zhí)行。當(dāng)worker執(zhí)行完一個(gè)任務(wù),如果時(shí)間片還未用完,并且任務(wù)隊(duì)列中也有任務(wù),那么worker會(huì)繼續(xù)取任務(wù)執(zhí)行,這樣可以減少不必要的切換。所以,我們可以猜測(cè)到Worker線程大致是一個(gè)循環(huán)。事實(shí)上,worker的確是一個(gè)循環(huán),主體邏輯就是不斷從隊(duì)列取任務(wù)然后執(zhí)行。除此以外,worker線程的執(zhí)行代碼還包含一些性能優(yōu)化措施和容錯(cuò)邏輯,例如第一個(gè)任務(wù)直接放在worker中而不放入隊(duì)列(所以worker每次先看自己的firstTask是否非空);任務(wù)代碼拋出異常導(dǎo)致線程終止后,重新創(chuàng)建一個(gè)線程以替換掉掛掉的這個(gè)線程等(見processWorkerExit方法的代碼)。根據(jù)對(duì)ThreadPoolExecutor.runWorker代碼的分析,得出worker的執(zhí)行邏輯如下圖所示:
要說(shuō)明的是,當(dāng)隊(duì)列為空時(shí)getTask會(huì)阻塞;當(dāng)調(diào)用了shutdown()之類的操作getTask會(huì)返回null,此時(shí)worker線程會(huì)判斷是否需要執(zhí)行清理操作。線程池的清理操作是由一個(gè)空閑worker線程完成的,且只有一個(gè)worker做清理工作,shutdown和shutdownNow只修改狀態(tài)和發(fā)出通知。更進(jìn)一步,即使把corePoolSize設(shè)為0以允許核心線程退出,線程池也至少保留一個(gè)線程用于做清理工作,只有關(guān)閉線程池之后,線程才全部退出。
每個(gè)worker其實(shí)是一個(gè)鎖(通過(guò)繼承AbstractQueuedSynchronizer),worker線程封裝在這個(gè)worker內(nèi)部。當(dāng)worker開始運(yùn)行之后,會(huì)對(duì)worker進(jìn)行加鎖動(dòng)作,等任務(wù)執(zhí)行完以后,再對(duì)worker執(zhí)行解鎖動(dòng)作(這是為了避免線程池的中斷信號(hào)影響任務(wù)代碼,線程池通過(guò)給worker發(fā)中斷,讓阻塞在getTask上的worker線程被喚醒來(lái))。所以,如果對(duì)這個(gè)worker執(zhí)行tryLock操作成功,那么這個(gè)worker是空閑的。
那么keepAliveTime到哪里去了?實(shí)際上,它在getTask方法中。每個(gè)worker線程通過(guò)計(jì)時(shí)的等待方法poll(keepAliveTime,TimeUnit.NANOSECONDS)來(lái)獲取任務(wù)。當(dāng)poll返回之后,說(shuō)明空閑時(shí)間已超過(guò)keepAliveTime時(shí)間。那么,核心線程與非核心線程如何區(qū)分?其實(shí),線程池中的worker線程都是對(duì)等的,不區(qū)分核心和非核心線程。在每次從隊(duì)列取任務(wù)之前,會(huì)先判斷一下當(dāng)前線程數(shù),如果當(dāng)前線程數(shù)大于corePoolSize,那么就用計(jì)時(shí)版本的poll取任務(wù),此時(shí)這個(gè)worker線程是非核心線程;而如果小于等于corePoolSize,則用不限時(shí)間等待方法take從隊(duì)列取任務(wù),此時(shí)它是核心線程。
6.其他細(xì)節(jié)
worker線程執(zhí)行任務(wù)前后會(huì)分別調(diào)用beforeExecute()和afterExecute()。
線程池在關(guān)閉之后會(huì)調(diào)用terminated()。
若要監(jiān)控線程池狀態(tài):getPoolSize()、getActiveCount()、getLargestPoolSize()、getTaskCount()、getCompletedTaskCount()。
最后線程池其實(shí)可以改進(jìn)的,我們不知道每個(gè)任務(wù)的執(zhí)行時(shí)間長(zhǎng)短,所以每個(gè)worker線程不區(qū)分runnable的大小,一般按照FIFO策略執(zhí)行任務(wù)。如果使用合理的規(guī)劃算法(動(dòng)態(tài)規(guī)劃),讓時(shí)間片的利用率最大化,能進(jìn)一步優(yōu)化線程池的性能,不過(guò)這可能設(shè)計(jì)起來(lái)非常復(fù)雜。
以上就是關(guān)于千鋒扣丁學(xué)堂Java培訓(xùn)之線程池ThreadPoolExecutor實(shí)現(xiàn)原理詳解的全部?jī)?nèi)容,
想要了解更多關(guān)于Java大數(shù)據(jù)方面內(nèi)容的小伙伴,請(qǐng)關(guān)注扣丁學(xué)堂Java大數(shù)據(jù)培訓(xùn)官網(wǎng)、微信等平臺(tái),扣丁學(xué)堂IT職業(yè)在線學(xué)習(xí)教育平臺(tái)為您提供權(quán)威的Java開發(fā)視頻,Java培訓(xùn)后的前景無(wú)限,行業(yè)薪資和未來(lái)的發(fā)展會(huì)越來(lái)越好的,扣丁學(xué)堂老師精心推出的Java視頻教程定能讓你快速掌握J(rèn)ava從入門到精通開發(fā)實(shí)戰(zhàn)技能??鄱W(xué)堂Java技術(shù)交流群:850353792。
【關(guān)注微信公眾號(hào)獲取更多學(xué)習(xí)資料】 【掃碼進(jìn)入JavaEE/微服務(wù)VIP免費(fèi)公開課】
查看更多關(guān)于“Java開發(fā)資訊”的相關(guān)文章>>
標(biāo)簽:
Java培訓(xùn)
Java視頻教程
Java多線程
Java面試題
Java學(xué)習(xí)視頻
springBoot項(xiàng)目