ThreadPoolExecutor.execute()源碼提供了大量注釋來解釋該方法的設(shè)計考慮。下面的源碼來自jdk1.6.0_37
使用這么多if-else就是為了性能考慮,減小鎖的使用范圍,避免execute方法整個執(zhí)行過程中都持有mainLock鎖。可以看到只有調(diào)用addIfUnderCorePoolSize、ensureQueuedTaskHandled、addIfUnderMaximumPoolSize這3個方法才需要持有鎖。如果新提交的任務(wù),不會進(jìn)入這3個方法,那么就不需要持有鎖。我們來看下,execute方法的設(shè)計是否能夠有效地減少進(jìn)入這3個方法的次數(shù),實現(xiàn)快進(jìn)快出。
第4行代碼poolSize >= corePoolSize,為什么要加這個判斷呢?
如果通過prestartAllCoreThreads事先啟動了所有核心線程,或者是提交的任務(wù)已經(jīng)讓當(dāng)前大小poolSize達(dá)到了核心大小 corePoolSize,并且核心線程不會死亡(allowCoreThreadTimeOut=false不允許核心線程超時退出,并且任務(wù)執(zhí)行過程沒有拋出異常導(dǎo)致線程退出),那么線程池中的線程數(shù)一定不會比corePoolSize小,此后如果再提交新任務(wù),那么就不會進(jìn)入addIfUnderCorePoolSize方法。poolSize >= corePoolSize就是為了減少進(jìn)入addIfUnderCorePoolSize函數(shù)的次數(shù),減少鎖的獲取和釋放次數(shù)。如果poolSize<corePoolSize,那么就會進(jìn)入addIfUnderCorePoolSize,該方法會在加鎖情況下,判斷線程池的狀態(tài)和當(dāng)前大小,然后決定是否需要新加線程來處理任務(wù)。由于addIfUnderCorePoolSize會持有mainLock鎖,所以可以防止其他線程對線程池的并發(fā)修改。也就說可以保證:在不需要添加新線程的時候,就不會添加。The call to addIfUnderCorePoolSize rechecks runState and pool size under lock (they change only under lock) so prevents false alarms that would add threads when it shouldn't。源碼中這段注釋,說的就是這個效果。
第5行代碼if (runState == RUNNING && workQueue.offer(command)),如果程序能走到這行代碼,從任務(wù)提交者的角度來看,此時線程池大小已經(jīng)達(dá)到了核心大小(雖然事實情況不一定如此,因為沒有加鎖,存在并發(fā)修改的可能;而且線程池中的線程也可能會死亡。如果滿足條件:
runState == RUNNING && workQueue.offer(command) 這意味著:線程池仍然處于運(yùn)行狀態(tài),并且任務(wù)排隊已經(jīng)成功。為什么程序執(zhí)行到這里依然不能結(jié)束,還是需要走之后的代碼呢?因為沒有加鎖,判斷條件不一定可靠。考慮下面2個場景:如果任務(wù)剛開始調(diào)用offer(還沒有成功地插入到阻塞隊列中),線程池中的線程全部死亡,那么就沒有線程來處理當(dāng)前提交的這個任務(wù)了;如果任務(wù)剛剛排隊成功,別的線程調(diào)用了shutdownNow()關(guān)閉了線程池,那么按照shutdownNow函數(shù)的語義,這個任務(wù)不應(yīng)該被處理。由于存在這2種特殊情況,所以必須進(jìn)行后續(xù)的處理。but
may also fail to add them when they should. This is compensated within the following steps.這就是說:addIfUnderCorePoolSize能夠保證不需要新線程的時候就不添加,但是不能保證需要添加新線程的時候就添加。所以讓任務(wù)排隊成功的時候,需要在鎖的保護(hù)下,判斷是否需要刪除這個任務(wù),或者是否需要新增線程來處理任務(wù)。
第6行代碼if (runState != RUNNING || poolSize == 0),如果任務(wù)成功排隊(workQueue.offer()返回true)后,線程池被關(guān)閉或者沒有存活的線程,那么就需要執(zhí)行ensureQueuedTaskHandled(command),也就是說這種情況下,任務(wù)可能沒有得到合適的處置。如果任務(wù)成功排隊后,線程池仍然處在運(yùn)行狀態(tài),而且有存活的線程,那么就能夠確保該新提交的任務(wù)一定會被處理。為什么會這樣呢?我們知道如果想關(guān)閉ThreadPoolExecutor,只有3種途徑:調(diào)用shutdown方法,調(diào)用shutdownNow方法,線程池最后一個工作者退出(對應(yīng)workerDone方法)。查看源碼我們知道,這3個可能導(dǎo)致線程池關(guān)閉的方法,最終都會調(diào)用tryTerminate()方法。也就是說如果線程池想要終止,就必須通過該方法。
當(dāng)線程池的線程池數(shù)是0的時候,如果線程池是running或者shutdown狀態(tài),并且任務(wù)隊列不為空,那么就會新增1個線程來處理任務(wù);如果線程池是狀態(tài),或者處于shutdown狀態(tài)并且任務(wù)隊列為空,那么線程池就會退出。也就是說,如果任務(wù)排隊成功后,線程池還沒有終止,那么該任務(wù)一定會得到合理的處置。
如果在任務(wù)插入之前或者插入的過程中,線程池不在運(yùn)行狀態(tài)runState != RUNNING 或者沒有存活的線程poolSize == 0,那么就需要自己考慮下:如何處理該提交的任務(wù),這通過ensureQueuedTaskHandled來完成。如果if (runState == RUNNING && workQueue.offer(command))條件是真,那么隨后if (runState != RUNNING || poolSize == 0)基本上很少出現(xiàn),大部分場景下都不需要執(zhí)行ensureQueuedTaskHandled方法,就不需要獲取和釋放鎖。下面我們通過源碼看下,ensureQueuedTaskHandled方法是如何處理異常場景的。
該方法持有mainLock鎖,所以可以防止線程池的并發(fā)修改。如果線程池不在running狀態(tài)(state != RUNNING ),并且新提交的任務(wù)還駐留在任務(wù)隊列中(workQueue.remove(command)返回true),那么當(dāng)前提交的任務(wù)會被拒絕執(zhí)行(調(diào)用reject(comma)方法)。也就是說,只要線程池不是running狀態(tài),那么就一定會拒絕執(zhí)行當(dāng)前提交的任務(wù),除非該任務(wù)已經(jīng)被線程池中的線程處理了(workQueue.remove返回false)。這里不區(qū)分到底是shutdown()關(guān)閉的線程池,還是通過shutdownNow關(guān)閉的線程池。如果新提交一個任務(wù),并且執(zhí)行流程進(jìn)入了ensureQueuedTaskHandled()函數(shù),那么該任務(wù)可能會被拒絕執(zhí)行,也可能會被正常執(zhí)行。如果線程池是running或者shutdown狀態(tài)(state < STOP),并且線程池中已經(jīng)沒有存活的線程,并且任務(wù)隊列非空,那么就需要新加1個線程,來處理等待執(zhí)行的任務(wù)。
可以看到:通過多次無鎖的條件判斷,能夠有效地減少提交任務(wù)時對mainLock鎖的競爭;也能夠確在并發(fā)執(zhí)行的情況下,當(dāng)前新提交的任務(wù)和線程池中等待執(zhí)行的任務(wù),都能得到合適的處理。不知道大師Doug Lea是如何想到這么巧妙的設(shè)計和實現(xiàn)execute()方法的!雖然execute方法能夠提高性能,但是犧牲了代碼的可讀性和簡易性。對于并發(fā)程序,還是那句經(jīng)典的老話make it right before you make it faster。