你敢信?掌握Java線程池原理,面試官會主動爲你加薪,受寵若驚

{"type":"doc","content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"今日分享開始啦,請大家多多指教~","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"今日分享 ThreadPoolExecutor 解析大全,現在很多人在許多公司面試中會被問到線程池。爲什麼面試官這麼熱衷於問線程池相關的面試題呢?因爲這是多線程的基礎,ThreadPoolExecutor 的幾個重要參數你要知道如何設置以及什麼場景選擇哪種 Executor 、線程池隊列的選擇以及相應的拒絕策略。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面是幾個大廠關於線程池的面試題:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池的使用場景","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池各個參數的含義,你平時用的什麼隊列以及拒絕策略?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"程序中哪些地方用到了線程池,用線程池的好處有哪些?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如何自己實現一個線程池","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"JDK 提供了哪些線程池的默認實現","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阿里巴巴 Java 開發手冊爲啥不允許默認實現的線程池","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池裏的參數你是怎麼得出來的,根據什麼算出來的?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"說說你自定義線程池裏的工作流程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"…","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這裏就不對面試題進行分析了,只講核心原理再結合動態調整線程池參數的實踐來幫助你對線程池有個清晰的認識,知道了原理再結合自己的實踐,那面試線程池也是得心應手了。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"一、線程池的概念","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1.1 線程池是什麼","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池是一種線程使用模式。線程過多會帶來額外的開銷,其中包括創建銷燬線程的開銷、調度線程的開銷等等,同時也降低了計算機的整體性能。線程池維護多個線程,等待監督管理者分配可併發執行的任務。這種做法,一方面避免了處理任務時創建銷燬線程開銷的代價,另一方面避免了線程數量膨脹導致的過分調度問題,保證了對內核的充分利用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1.2 使用線程池的好處","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"降低資源消耗:通過池化技術重複利用已創建的線程,降低線程創建和銷燬造成的損耗。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提高響應速度:任務到達時,無需等待線程創建即可立即執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提高線程的可管理性:線程是稀缺資源,如果無限制創建,不僅會消耗系統資源,還會因爲線程的不合理分佈導致資源調度失衡,降低系統的穩定性。使用線程池可以進行統一的分配、調優和監控。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提供更多更強大的功能:線程池具備可拓展性,允許開發人員向其中增加更多的功能。比如延時定時線程池 ScheduledThreadPoolExecutor,就允許任務延期執行或定期執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"1.3、ThreadPoolExecutor 的核心參數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"網上說的天花亂墜的,也不如直接看 Doug Lea 大佬源碼的註釋來的更加貼切些。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/98/988eddfa774a178a2b6a4fd12247b334.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"corePoolSize","attrs":{}},{"type":"text","text":":the number of threads to keep in the pool, even if they are idle, unless {@code allowCoreThreadTimeOut} is set","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"核心線程數","attrs":{}},{"type":"text","text":":線程池中保留的線程數,即使它們是空閒的,除非設置 allowCoreThreadTimeOut。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"maximumPoolSize","attrs":{}},{"type":"text","text":":the maximum number of threads to allow in the pool","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"最大線程數","attrs":{}},{"type":"text","text":":線程池中允許的最大線程數","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"keepAliveTime","attrs":{}},{"type":"text","text":":when the number of threads is greater than the core, this is the maximum time that excess idle threads will wait for new tasks before terminating.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"線程空閒時間","attrs":{}},{"type":"text","text":":如果經過 keepAliveTime 時間後,超過核心線程數的線程還沒有接受到新的任務,那就回收。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"unit","attrs":{}},{"type":"text","text":":the time unit for the {@code keepAliveTime} argument","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"單位","attrs":{}},{"type":"text","text":":keepAliveTime 的時間單位","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"workQueue","attrs":{}},{"type":"text","text":":the queue to use for holding tasks before they are executed. This queue will hold only the {@code Runnable} tasks submitted by the {@code execute} method.","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"存放待執行任務的隊列","attrs":{}},{"type":"text","text":":當提交的任務數超過核心線程數後,再提交的任務就存放在這裏。它僅僅用來存放被 execute 方法提交的 Runnable 任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"threadFactory","attrs":{}},{"type":"text","text":":the factory to use when the executor creates a new thread","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"線程工廠","attrs":{}},{"type":"text","text":":執行程序創建新線程時使用的工廠。比如我們項目中自定義的線程工廠,排查問題的時候,根據線程工廠的名稱就知道這個線程來自哪裏,很快地定位出問題,","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"handler ","attrs":{}},{"type":"text","text":":the handler to use when execution is blocked because the thread bounds and queue capacities are reached","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"拒絕策略","attrs":{}},{"type":"text","text":":當隊列裏面放滿了任務、最大線程數的線程都在工作時,這時繼續提交的任務線程池就處理不了,應該執行怎麼樣的拒絕策略。","attrs":{}}]},{"type":"heading","attrs":{"align":null,"level":1},"content":[{"type":"text","text":"二、線程池的實現原理","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}}],"text":"本文描述線程池是 JDK 8 中提供的 ThreadPoolExecutor 類,那我們就從 ThreadPoolExecutor 類來看下它的 UML 依賴關係。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}},{"type":"strong","attrs":{}}],"text":"2.1 總體設計","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d2/d24c681d2d08a9b4c51e35d2a0627f85.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"藍色實線:繼承關係","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"綠色虛線:接口實現關係","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"綠色實現:接口繼承關係","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ThreadPoolExecutor 實現的頂層接口是 Executor,頂層接口只提供了void execute(Runnable command); 這麼一個方法,Executor 提供的是一種思想:將任務提交和任務執行進行解耦。用戶無需關注如何創建線程,如何調度線程來執行任務,用戶只需提供 Runnable 對象,將任務的運行邏輯提交到執行器(Executor)中,由 Executor 框架完成線程的調配和任務的執行部分。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ExecutorService 接口增加了一些能力:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"擴充執行任務的能力,補充可以爲一個或一批異步任務生成 Future 的方法;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"提供了管控線程池的方法,比如停止線程池的運行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"AbstractExecutorService 則是上層的抽象類,將執行任務的流程串聯了起來,保證下層的實現只需關注一個執行任務的方法即可。最下層的實現類 ThreadPoolExecutor 實現最複雜的運行部分,ThreadPoolExecutor 將會一方面維護自身的生命週期,另一方面同時管理線程和任務,使兩者良好的結合從而執行並行任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們來看下 ThreadPoolExecutor 的運行流程:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fe/fe9c3a490a44b5161c280ae2c814802d.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池在內部實際上構建了一個生產者消費者模型,將線程和任務兩者解耦,並不直接關聯,從而良好的緩衝任務,複用線程。線程池的運行主要分成兩部分:任務管理、線程管理。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"任務管理部分充當生產者的角色,當任務提交後,線程池會判斷該任務後續的流轉:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"直接申請線程執行該任務","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"緩衝到隊列中等待線程執行","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"拒絕該任務","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程管理部分充當消費者的角色,它們被統一維護在線程池內,根據任務請求進行線程的分配,當線程執行完任務後則會繼續獲取新的任務去執行,最終當線程獲取不到任務的時候,線程就會被回收。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面就從以下三個核心機制來詳細講解線程池運行機制:","attrs":{}}]},{"type":"bulletedlist","content":[{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"線程池如何維護自身狀態","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"線程池如何管理任務","attrs":{}}]}]},{"type":"listitem","attrs":{"listStyle":null},"content":[{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#000000","name":"user"}}],"text":"線程池如何管理線程","attrs":{}}]}]}],"attrs":{}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}},{"type":"strong","attrs":{}}],"text":"2.2 線程池如何維護自身狀態","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}}],"text":"線程池運行的狀態,並不是用戶顯式設置的,而是伴隨着線程池的運行,由內部來維護。線程池內部使用一個變量維護兩個值:運行狀態(runState)和線程數量(workerCount)。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7e/7ebe98adbb1bc939c9b8094f1409cad0.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"ctl 這個 AtomicInteger 類型,是對線程池的運行狀態和線程池中有效線程的數量進行控制的一個字段, 它同時包含兩部分的信息:線程池的運行狀態 (runState) 和線程池內有效線程的數量 (workerCount),高 3 位保存 runState,低 29 位保存 workerCount,兩個變量之間互不干擾。用一個變量去存儲兩個值,可避免在做相關決策時,出現不一致的情況,不必爲了維護兩者的一致,而佔用鎖資源。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"通過閱讀線程池源代碼也可以發現,經常出現要同時判斷線程池運行狀態和線程數量的情況。線程池也提供了若干方法去供用戶獲得線程池當前的運行狀態、線程個數。這裏都使用的是位運算的方式,相比於基本運算,速度也會快很多。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"關於內部封裝的獲取生命週期狀態、獲取線程池線程數量的計算方法如下代碼:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/33/337c01dc39944361158c1081c2d3efef.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"爲什麼一個整型變量既可以保存運行狀態,又可以保存線程數量?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,我們知道 Java 中 1 個整型佔 4 個字節,也就是 32 位,所以 1 個整型有 32 位。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以整型 1 用二進制表示就是:0000 0000 0000 0000 0000 0000 0000 0001","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"整型 -1 用二進制表示就是:1111 1111 1111 1111 1111 1111 1111 1111 (這個是補碼,這個忘了的話那得去複習下原碼、反碼、補碼等計算機基礎知識了。)","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在 ThreadPoolExecutor,整型中 32 位的前 3 位用來表示線程池狀態,後 29 位表示線程池中有效的線程數。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d0/d034031afd8faa4540d2ed3bfbad0c01.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"CAPACITY = (1 << 29) - 1 得到 0001 1111 1111 1111 1111 1111 1111 1111,帶你分析下 CAPACITY 怎麼來的,下面的那些狀態大家也可以自己去分析一下。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們先來看 1 << 29,首先看 1 的二進制代表 0000 0000 0000 0000 0000 0000 0000 0001。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"然後 0000 0000 0000 0000 0000 0000 0000 0001 向左移 29 位,得到 0010 0000 0000 0000 0000 0000 0000 0000。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"最後將 0010 0000 0000 0000 0000 0000 0000 0000 減 1 得到 0001 1111 1111 1111 1111 1111 1111 1111。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們下面再來了解下 ThreadPoolExecutor 所定義的狀態,這些狀態都和線程的執行密切相關:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fa/fa25ca108c7ff797f68cd9f62778a57b.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"RUNNING:能接受新提交的任務,並且也能處理阻塞隊列中的任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"SHUTDOWN:指調用了 shutdown() 方法,不再接受新提交的任務,但卻可以繼續處理阻塞隊列中已保存的任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"STOP:指調用了 shutdownNow() 方法,不再接受新提交的任務,同時拋棄阻塞隊列裏的所有任務並中斷所有正在執行任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TIDYING: 所有任務都執行完畢,workerCount 有效線程數爲 0。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"TERMINATED:終止狀態,當執行 terminated() 後會更新爲這個狀態。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fd/fd9f55a0c70db562c66af697384acb45.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.3 線程池如何管理任務","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.3.1 任務調度","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"任務調度是線程池的主要入口,當用戶提交了一個任務,接下來這個任務將如何執行都是由這個階段決定的。瞭解這部分就相當於瞭解了線程池的核心運行機制。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先,所有任務的調度都是由 execute 方法完成的,比如我們業務代碼中","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"threadPool.execute(new Job());。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這部分完成的工作是:檢查現在線程池的運行狀態、運行線程數、運行策略,決定接下來執行的流程,是直接申請線程執行,或是緩衝到隊列中執行,亦或是直接拒絕該任務。其執行過程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"首先檢測線程池運行狀態,如果不是 RUNNING,則直接拒絕,線程池要保證在 RUNNING 的狀態下執行任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 workerCount < corePoolSize,則創建並啓動一個線程來執行新提交的任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 workerCount >= corePoolSize,且線程池內的阻塞隊列未滿,則將任務添加到該阻塞隊列中。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 workerCount >= corePoolSize && workerCount < maximumPoolSize,且線程池內的阻塞隊列已滿,則創建並啓動一個線程來執行新提交的任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 workerCount >= maximumPoolSize,並且線程池內的阻塞隊列已滿,則根據拒絕策略來處理該任務,默認的處理方式是直接拋異常。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"執行流程圖如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/8b/8b77e3f0227e7a9cfe99bbf93e057960.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.3.2 待執行任務的隊列","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"待執行任務的隊列是線程池能夠管理任務的核心部分。線程池的本質是對任務和線程的管理,而做到這一點最關鍵的思想就是將任務和線程兩者解耦,不讓兩者直接關聯,纔可以做後續的分配工作。線程池中是以生產者消費者模式,通過一個阻塞隊列來實現的。阻塞隊列緩存任務,工作線程從阻塞隊列中獲取任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阻塞隊列(BlockingQueue)是一個支持兩個附加操作的隊列。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這兩個附加的操作是:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"在隊列爲空時,獲取元素的線程會等待隊列變爲非空。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當隊列滿時,存儲元素的線程會等待隊列可用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"阻塞隊列常用於生產者和消費者的場景,生產者是往隊列裏添加元素的線程,消費者是從隊列裏拿元素的線程。阻塞隊列就是生產者存放元素的容器,而消費者也只從容器裏拿元素。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下圖中展示了 Thread1 往阻塞隊列中添加元素,而線程 Thread2 從阻塞隊列中移除元素:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/1c/1cec00b4910cac068da47360aadb0531.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}}],"text":"使用不同的隊列可以實現不一樣的任務存取策略。我們下面來看下阻塞隊列的成員:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/7f/7f6a34e90b3583c57fc31679b0b5f7bb.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.3.3 任務申請","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"從上文可知,任務的執行有兩種可能:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"一種是任務直接由新創建的線程執行","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"另一種是線程從任務隊列中獲取任務然後執行,執行完任務的空閒線程會再次去從隊列中申請任務再去執行。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"第一種情況僅出現在線程初始創建的時候,第二種是線程獲取任務絕大多數的情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程需要從待執行任務的隊列中不斷地取任務執行,幫助線程從阻塞隊列中獲取任務,實現線程管理模塊和任務管理模塊之間的通信。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"這部分策略由 getTask 方法實現,我們來看下 getTask 方法的代碼。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/40/40ba6bd421f0b9a47af11638692bc322.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"getTask 方法在阻塞隊列中有待執行的任務時會從隊列中彈出一個任務並返回,如果阻塞隊列爲空,那麼就會阻塞等待新的任務提交到隊列中直到超時(在一些配置下會一直等待而不超時),如果在超時之前獲取到了新的任務,那麼就會將這個任務作爲返回值返回。所以一般 getTask 方法是不會返回 null 的,只會阻塞等待下一個任務並在之後將這個新任務作爲返回值返回。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當 getTask 方法返回 null 時會導致當前 Worker 退出,當前線程被銷燬。在以下情況下 getTask 方法纔會返回 null:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當前線程池中的線程數超過了最大線程數。這是因爲運行時通過調用 setMaximumPoolSize 修改了最大線程數而導致的結果;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池處於 STOP 狀態。這種情況下所有線程都應該被立即回收銷燬;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池處於 SHUTDOWN 狀態,且阻塞隊列爲空。這種情況下已經不會有新的任務被提交到阻塞隊列中了,所以線程應該被銷燬;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程可以被超時回收的情況下等待新任務超時。線程被超時回收一般有以下兩種情況:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"允許核心線程超時(線程池配置)的情況下線程等待任務超時","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"超出核心線程數部分的線程等待任務超時","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.3.4 任務拒絕","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"任務拒絕模塊是線程池的保護部分,線程池有一個最大的容量,當線程池的任務緩存隊列已滿,並且線程池中的線程數目達到 maximumPoolSize 時,就需要拒絕掉該任務,採取任務拒絕策略,保護線程池。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"拒絕策略是一個接口,其設計如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/f3/f37d0fc90af25e3e344ba2f14bbbe2ce.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}}],"text":"用戶可以通過實現這個接口去定製拒絕策略,也可以選擇 JDK 提供的四種已有拒絕策略,其特點如下:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/0c/0c51b41287bdffef459d442749a4e7fb.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}},{"type":"strong","attrs":{}}],"text":"2.4 線程池如何管理線程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}},{"type":"strong","attrs":{}}],"text":"2.4.1 Worker線程","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}}],"text":"線程池爲了掌握線程的狀態並維護線程的生命週期,設計了線程池內的工作線程 Worker。我們來看一下它的代碼:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/dc/dc5022d20369d366425491a161d06958.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Worker 這個工作線程,實現了 Runnable 接口,並持有一個線程thread,一個初始化的任務firstTask。thread 是在調用構造方法時通過 ThreadFactory 來創建的線程,可以用來執行任務;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"firstTask 用它來保存傳入的第一個任務,這個任務可以有也可以爲 null。如果這個值是非空的,那麼線程就會在啓動初期立即執行這個任務,也就對應核心線程創建時的情況;如果這個值是空的,那麼就需要創建一個線程去執行任務列表(workQueue)中的任務,也就是非核心線程的創建。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.4.1.1 AQS 作用","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Worker 繼承了 AbstractQueuedSynchronizer,主要目的有兩個:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"將鎖的粒度細化到每個 Worker","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果多個 Worker 使用同一個鎖,那麼一個 Worker Running 持有鎖的時候,其他 Worker 就無法執行,這顯然是不合理的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"直接使用 CAS 獲取,避免阻塞。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果這個鎖使用阻塞獲取,那麼在多 Worker 的情況下執行 shutDown。如果這個 Worker 此時正在 Running 無法獲取到鎖,那麼執行 shutDown() 線程就會阻塞住了,顯然是不合理的。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.4.1.2 Runnable 作用","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Worker 還實現了 Runnable,它有兩個屬性 thead、firstTask。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"firstTask 用它來保存傳入的第一個任務,這個任務可以有也可以爲 null。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果這個值是非空的,那麼線程就會在啓動初期立即執行這個任務,也就對應核心線程創建時的情況。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果這個值是 null,那麼就需要創建一個線程去執行任務列表(workQueue)中的任務,也就是非核心線程的創建。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"根據整體流程:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池調用 execute —> 創建 Worker(設置屬性thead、firstTask)—> worker.thread.start() —> 實際上調用的是 worker.run() —> 線程池的 runWorker(worker) —> worker.firstTask.run() (如果 firstTask 爲 null 就從等待隊列中拉取一個)。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"Worker 執行任務的模型如下圖所示:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/72/72b6fba109f694d2dd232cf7e5f10d96.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.4.2 Worker 線程增加","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"增加線程是通過線程池中的 addWorker 方法,該方法的功能就是增加一個線程,該方法不考慮線程池是在哪個階段增加的該線程,這個分配線程的策略是在上個步驟完成的,該步驟僅僅完成增加線程,並使它運行,最後返回是否成功這個結果。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"addWorker 方法有兩個參數:firstTask、core。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"firstTask 參數用於指定新增的線程執行的第一個任務,該參數可以爲空;","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"core 參數爲 true 表示在新增線程時會判斷當前活動線程數是否少於 corePoolSize,false 表示新增線程前需要判斷當前活動線程數是否少於 maximumPoolSize。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"我們來看一下 addWorker 的源碼:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/65/651e601229616741c9f7ced4db083f87.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}}],"text":"源碼看着是不是挺費勁的?沒關係,再看一張執行流程圖加深下印象。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/a7/a7db9f7187f3d052c7a18b8389a8e795.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}},{"type":"strong","attrs":{}}],"text":"2.4.3 Worker 線程執行任務","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}}],"text":"Worker 中的線程 start 的時候,調用 Worker 本身 run 方法,這個 run 方法調用外部類ThreadPoolExecutor 的 runWorker 方法,直接看 runWorker 方法的源碼:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/17/17273acc8f8e25b4da405ade17e7898c.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"執行流程如下:","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"while 循環不斷地通過 getTask() 方法獲取任務","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"getTask() 方法從阻塞隊列中取任務","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果線程池正在停止,那麼要保證當前線程是中斷狀態,否則要保證當前線程不是中斷狀態。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"執行任務","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"如果 getTask 結果爲 null 則跳出循環,執行 processWorkerExit() 方法,銷燬線程。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/fe/fe1624cdc5c1189252e2db585ddf5d49.jpeg","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.4.4 Worker 線程回收","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程池中線程的銷燬依賴 JVM 自動的回收,線程池做的工作是根據當前線程池的狀態維護一定數量的線程引用,防止這部分線程被 JVM 回收,當線程池決定哪些線程需要回收時,只需要將其引用消除即可。Worker 被創建出來後,就會不斷地進行輪詢,然後獲取任務去執行,核心線程可以無限等待獲取任務,非核心線程要限時獲取任務。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"當 Worker 無法獲取到任務,也就是獲取的任務爲空時,循環會結束,Worker 會主動消除自身在線程池內的引用。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"線程回收的工作是在 processWorkerExit 方法完成的。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/9a/9a4fc2f0bba0bed12219959955b8a68a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}}],"text":"在回收 Worker 的時候線程池會嘗試結束自己的運行,tryTerminate 方法:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/5b/5b8ad68387115662fa8d14d8f5751e0a.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}},{"type":"strong","attrs":{}}],"text":"2.4.4 Worker 線程關閉","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}}],"text":"說到線程關閉,我們就不得不來說說 shutdown 方法和 shutdownNow 方法。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}},{"type":"strong","attrs":{}}],"text":"2.4.4.1 shutdown","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/30/303a553558c663d769077818626af453.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"interruptIdleWorkers 方法,注意,這個方法打斷的是閒置 Worker,打斷閒置 Worker 之後,getTask 方法會返回 null,然後 Worker 會被回收。那什麼是閒置 Worker 呢?","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"閒置 Worker 是這樣解釋的:Worker 運行的時候會去阻塞隊列拿數據(getTask方法),拿的時候如果沒有設置超時時間,那麼會一直阻塞等待阻塞隊列進數據,這樣的 Worker 就被稱爲閒置 Worker。由於 Worker 也是一個 AQS,在 runWorker 方法裏會有一對 lock 和 unlock 操作,這對 lock 操作是爲了確保 Worker 不是一個閒置 Worker。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"所以 Worker 被設計成一個 AQS 是爲了根據 Worker 的鎖來判斷是否是閒置線程,是否可以被強制中斷。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"下面我們看下 interruptIdleWorkers 方法:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/12/122fd855141132d71de11a6517ae25d7.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.4.4.2 shutdownNow","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"shutdown 方法將線程池狀態改成 SHUTDOWN,線程池還能繼續處理阻塞隊列裏的任務,並且會回收一些閒置的 Worker。但是 shutdownNow 方法不一樣,它會把線程池狀態改成 STOP 狀態,這樣不會處理阻塞隊列裏的任務,也不會處理新的任務。","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/d1/d1b437f8dc3884c99a67712f854fb936.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#4D4D4D","name":"user"}}],"text":"shutdownNow 的中斷和 shutdown 方法不一樣,調用的是 interruptWorkers 方法:","attrs":{}}]},{"type":"image","attrs":{"src":"https://static001.geekbang.org/infoq/00/00ea290de80acae1ef77e879115246ca.png","alt":null,"title":null,"style":[{"key":"width","value":"75%"},{"key":"bordertype","value":"none"}],"href":null,"fromPaste":true,"pastePass":true}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"strong","attrs":{}}],"text":"2.4.4.3 Worker 線程關閉小結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"shutdown 方法會更新狀態到 SHUTDOWN,不會影響阻塞隊列裏任務的執行,但是不會執行新進來的任務。同時也會回收閒置的 Worker,閒置 Worker 的定義上面已經說過了。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"shutdownNow 方法會更新狀態到 STOP,會影響阻塞隊列的任務執行,也不會執行新進來的任務。同時會回收所有的 Worker。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"小結","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","text":"很多人平時也沒有用到線程池,用的都是定義類繼承 Thread類 或者 定義類實現 Runnable 接口來實現多線程的。但如果你是面試的 Java 中高級開發,那你千萬不要這樣說,這會讓面試官一下覺得你不值中高級。如果你面的中高級還不知道線程池的話也沒關係,現在瞭解還不算晚;如果你是已經用過線程池相關的,那也會讓你對線程池的原理更加清楚,在項目中應用也會得心應手。","attrs":{}}]},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null}},{"type":"paragraph","attrs":{"indent":0,"number":0,"align":null,"origin":null},"content":[{"type":"text","marks":[{"type":"color","attrs":{"color":"#333333","name":"user"}}],"text":"今日份分享已結束,請大家多多包涵和指點!","attrs":{}}]}]}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章