摘要:我們就來看看線程池中那些非常重要的接口和抽象類,深度分析下線程池中是如何将抽象這一思想運用的淋漓盡緻的。
本文分享自華為雲社群《【高并發】深度解析線程池中那些重要的頂層接口和抽象類》,作者:冰 河。
通過對線程池中接口和抽象類的分析,你會發現,整個線程池設計的是如此的優雅和強大,從線程池的代碼設計中,我們學到的不隻是代碼而已!!
題外話:膜拜Java大神Doug Lea,Java中的并發包正是這位老爺子寫的,他是這個世界上對Java影響力最大的一個人。
說起線程池中提供的重要的接口和抽象類,基本上就是如下圖所示的接口和類。

接口與類的簡單說明:
Executor接口:這個接口也是整個線程池中最頂層的接口,提供了一個無傳回值的送出任務的方法。
ExecutorService接口:派生自Executor接口,擴充了很過功能,例如關閉線程池,送出任務并傳回結果資料、喚醒線程池中的任務等。
AbstractExecutorService抽象類:派生自ExecutorService接口,實作了幾個非常實作的方法,供子類進行調用。
ScheduledExecutorService定時任務接口,派生自ExecutorService接口,擁有ExecutorService接口定義的全部方法,并擴充了定時任務相關的方法。
接下來,我們就分别從源碼角度來看下這些接口和抽象類從頂層設計上提供了哪些功能。
Executor接口的源碼如下所示。
從源碼可以看出,Executor接口非常簡單,隻提供了一個無傳回值的送出任務的execute(Runnable)方法。
由于這個接口過于簡單,我們無法得知線程池的執行結果資料,如果我們不再使用線程池,也無法通過Executor接口來關閉線程池。此時,我們就需要ExecutorService接口的支援了。
ExecutorService接口是非定時任務類線程池的核心接口,通過ExecutorService接口能夠向線程池中送出任務(支援有傳回結果和無傳回結果兩種方式)、關閉線程池、喚醒線程池中的任務等。ExecutorService接口的源碼如下所示。
關于ExecutorService接口中每個方法的含義,直接上述接口源碼中的注釋即可,這些接口方法都比較簡單,我就不一一重複列舉描述了。這個接口也是我們在使用非定時任務類的線程池中最常使用的接口。
AbstractExecutorService類是一個抽象類,派生自ExecutorService接口,在其基礎上實作了幾個比較實用的方法,提供給子類進行調用。我們還是來看下AbstractExecutorService類的源碼。
注意:大家可以到java.util.concurrent包下檢視完整的AbstractExecutorService類的源碼,這裡,我将AbstractExecutorService源碼進行拆解,詳解每個方法的作用。
RunnableFuture類用于擷取執行結果,在實際使用時,我們經常使用的是它的子類FutureTask,newTaskFor方法的作用就是将任務封裝成FutureTask對象,後續将FutureTask對象送出到線程池。
這個方法是批量執行線程池的任務,最終傳回一個結果資料的核心方法,通過源代碼的分析,我們可以發現,這個方法隻要擷取到一個結果資料,就會取消線程池中所有運作的任務,并将結果資料傳回。這就好比是很多要進入一個居民小區一樣,隻要有一個人有門禁卡,門衛就不再檢查其他人是否有門禁卡,直接放行。
在上述代碼中,我們看到送出任務使用的ExecutorCompletionService對象的submit方法,我們再來看下ExecutorCompletionService類中的submit方法,如下所示。
可以看到,ExecutorCompletionService類中的submit方法本質上調用的還是Executor接口的execute方法。
這兩個invokeAny方法本質上都是在調用doInvokeAny方法,線上程池中送出多個任務,隻要傳回一個結果資料即可。
直接看上面的代碼,大家可能有點暈。這裡,我舉一個例子,我們在使用線程池的時候,可能會啟動多個線程去執行各自的任務,比如線程A負責task_a,線程B負責task_b,這樣可以大規模提升系統處理任務的速度。如果我們希望其中一個線程執行完成傳回結果資料時立即傳回,而不需要再讓其他線程繼續執行任務。此時,就可以使用invokeAny方法。
invokeAll方法同樣實作了無逾時時間設定和有逾時時間設定的邏輯。
無逾時時間設定的invokeAll方法總體邏輯為:将所有任務封裝成RunnableFuture對象,調用execute方法執行任務,将傳回的結果資料添加到futures集合,之後對futures集合進行周遊判斷,檢測任務是否完成,如果沒有完成,則調用get方法阻塞任務,直到傳回結果資料,此時會忽略異常。最終在finally代碼塊中對所有任務是否完成的辨別進行判斷,如果存在未完成的任務,則取消已經送出的任務。
有逾時設定的invokeAll方法總體邏輯與無逾時時間設定的invokeAll方法總體邏輯基本相同,隻是在兩個地方添加了逾時的邏輯判斷。一個是在添加執行任務時進行逾時判斷,如果逾時,則立刻傳回futures集合;另一個是每次對結果資料進行判斷時添加了逾時處理邏輯。
invokeAll方法中本質上還是調用Executor接口的execute方法來送出任務。
submit方法的邏輯比較簡單,就是将任務封裝成RunnableFuture對象并送出,執行任務後傳回Future結果資料。如下所示。
從源碼中可以看出submit方法送出任務時,本質上還是調用的Executor接口的execute方法。
綜上所述,在非定時任務類的線程池中送出任務時,本質上都是調用的Executor接口的execute方法。至于調用的是哪個具體實作類的execute方法,我們在後面的文章中深入分析。
ScheduledExecutorService接口派生自ExecutorService接口,繼承了ExecutorService接口的所有功能,并提供了定時處理任務的能力,ScheduledExecutorService接口的源代碼比較簡單,如下所示。
至此,我們分析了線程池體系中重要的頂層接口和抽象類。
通過對這些頂層接口和抽象類的分析,我們需要從中感悟并體會軟體開發中的抽象思維,深入了解抽象思維在具體編碼中的實作,最終,形成自己的程式設計思維,運用到實際的項目中,這也是我們能夠從源碼中所能學到的衆多細節之一。這也是進階或資深工程師和架構師必須了解源碼細節的原因之一。
點選關注,第一時間了解華為雲新鮮技術~