自己動手實現線程池 jdk線程池ThreadPoolExecutor工作原理解析(一)

jdk線程池ThreadPoolExecutor工作原理解析(自己動手實現線程池)(一)線程池介紹在日常開發中經常會遇到需要使用其它線程將大量任務異步處理的場景(異步化以及提升系統的吞吐量),而在使用線程的過程中卻存在著兩個痛點 。

  1. 在java等很多主流語言中每個邏輯上的線程底層都對應著一個系統線程(不考慮虛擬線程的情況) 。操作系統創建一個新線程是存在一定開銷的,在需要執行大量的異步任務時 , 如果處理每個任務時都直接向系統申請創建一個線程來執行,并在任務執行完畢后再回收線程,則創建/銷毀大量線程的開銷將無法忍受 。
  2. 每個系統線程都會占用一定的內存空間,且系統在調度不同線程上下文切換時存在一定的cpu開銷 。因此在一定的硬件條件下 , 操作系統能同時維護的系統線程個數相對而言是比較有限的 。在使用線程的過程中如果沒有控制好流量,會很容易創建過多的線程而耗盡系統資源 , 令系統變得不可用 。
而線程池正是為解決上述痛點而生的,其通過兩個手段來解決上述痛點 。
池化線程資源池化線程資源,顧名思義就是維護一個存活線程的集合(池子) 。提交任務的用戶程序不直接控制線程的創建和銷毀,不用每次執行任務時都申請創建一個新線程,而是通過線程池間接的獲得線程去處理異步任務 。線程池中的線程在執行完任務后通常也不會被系統回收掉 , 而是繼續待在池子中用于執行其它的任務(執行堆積的待執行任務或是等待新任務) 。線程池通過池化線程資源,避免了系統反復創建/銷毀線程的開銷,大幅提高了處理大規模異步任務時的性能 。
對線程資源的申請進行收口,限制系統資源的使用如果程序都統一使用線程池來處理異步任務,則線程池內部便可以對系統資源的使用施加一定限制 。例如用戶可以指定一個線程池最大可維護的線程數量,以避免耗盡系統資源 。當用戶提交任務的速率過大 , 導致線程池中的線程數到達指定的最大值時依然無法滿足需求時,線程池可以通過丟棄部分任務或限制提交任務的流量的方式來處理這一問題 。線程池通過對線程資源的使用進行統一收口,用戶可以通過設置線程池的參數來控制系統資源的使用 , 從而避免系統資源耗盡 。
jdk線程池ThreadPoolExecutor簡單介紹前面介紹了線程池的概念,而要深入理解線程池的工作原理最好的辦法便是找到一個優秀的線程池實現來加以研究 。而自jdk1.5中引入的通用線程池框架ThreadPoolExecutor便是一個很好的學習對象 。其內部實現不算復雜,卻在高效實現核心功能的同時還提供了較豐富的拓展能力 。
下面從整體上介紹一下jdk通用線程池ThreadPoolExecutor的工作原理(基于jdk8) 。
ThreadPoolExecutor運行時工作流程首先ThreadPoolExecutor允許用戶從兩個不同維度來控制線程資源的使用,即最大核心線程數(corePoolSize)和最大線程數(maximumPoolSize) 。最大核心線程數:核心線程指的是通常常駐線程池的線程 。常駐線程在線程池沒有任務空閑時也不會被銷毀,而是處于idle狀態,這樣在新任務到來時就能很快的進行響應 。最大線程數:和第一節中提到的一樣,即線程池中所能允許的活躍線程的最大數量 。
在向ThreadPoolExecutor提交任務時(execute方法),會執行一系列的判斷來決定任務應該如何被執行(源碼在下一節中具體分析) 。
  1. 首先判斷當前活躍的線程數是否小于指定的最大核心線程數corePoolSize 。如果為真,則說明當前線程池還未完成預熱,核心線程數不飽和,創建一個新線程來執行該任務 。如果為假,則說明當前線程池已完成預熱 , 進行下一步判斷 。
  2. 嘗試將當前任務放入工作隊列workQueue(阻塞隊列BlockingQueue),工作隊列中的任務會被線程池中的活躍線程按入隊順序逐個消費 。如果入隊成功,則說明當前工作隊列未滿,入隊的任務將會被線程池中的某個活躍線程所消費并執行 。如果入隊失敗,則說明當前工作隊列已飽和,線程池消費任務的速度可能太慢了 , 可能需要創建更多新線程來加速消費,進行下一步判斷 。
  3. 判斷當前活躍的線程數是否小于指定的最大線程數maximumPoolSize 。如果為真,則說明當前線程池所承載的線程數還未達到參數指定的上限,還有余量來創建新的線程加速消費,創建一個新線程來執行該任務 。如果為假,則說明當前線程池所承載的線程數達到了上限,但處理任務的速度依然不夠快,需要觸發拒絕策略 。

    推薦閱讀