Tomcat/Jetty 是目前比較流行的 Web 容器,二者接受請求以後都會轉交給線程池處理,這樣能夠有效提升處理的能力與併發度。JDK 提升完整線程池實現,可是 Tomcat/Jetty 都沒有直接使用。Jetty 採用自研方案,內部實現 QueuedThreadPool
線程池組件,而 Tomcat 採用擴展方案,踩在 JDK 線程池的肩膀上,擴展 JDK 原生線程池。html
JDK 原生線程池能夠說功能比較完善,使用也比較簡單,那爲什麼 Tomcat/Jetty 卻不選擇這個方案,反而本身去動手實現那?java
一般咱們能夠將執行的任務分爲兩類:數據庫
cpu 密集型任務,須要線程長時間進行的複雜的運算,這種類型的任務須要少建立線程,過多的線程將會頻繁引發上文切換,下降任務處理處理速度。安全
而 io 密集型任務,因爲線程並非一直在運行,可能大部分時間在等待 IO 讀取/寫入數據,增長線程數量能夠提升併發度,儘量多處理任務。併發
JDK 原生線程池工做流程以下:ide
詳情能夠查看 一文教你安全的關閉線程池,上圖假設使用
LinkedBlockingQueue
。高併發
靈魂拷問:上述流程是否記錯過?在很長一段時間內,我都認爲線程數量到達最大線程數,才放入隊列中。 ̄□ ̄||idea
上圖中能夠發現只要線程池線程數量大於核心線程數,就會先將任務加入到任務隊列中,只有任務隊列加入失敗,纔會再新建線程。也就是說原生線程池隊列未滿以前,最多隻有核心線程數量線程。spa
這種策略顯然比較適合處理 cpu
密集型任務,可是對於 io
密集型任務,如數據庫查詢,rpc 請求調用等,就不是很友好了。線程
因爲 Tomcat/Jetty 須要處理大量客戶端請求任務,若是採用原生線程池,一旦接受請求數量大於線程池核心線程數,這些請求就會被放入到隊列中,等待覈心線程處理。這樣作顯然下降這些請求整體處理速度,因此二者都沒采用 JDK 原生線程池。
解決上面的辦法能夠像 Jetty 本身實現線程池組件,這樣就能夠更加適配內部邏輯,不過開發難度比較大,另外一種就像 Tomcat 同樣,擴展原生 JDK 線程池,實現比較簡單。
下面主要以 Tomcat 擴展線程池,講講其實現原理。
首先咱們從 JDK 線程池源碼出發,查看如何這個基礎上擴展。
能夠看到線程池流程主要分爲三步,第二步根據 queue#offer
方法返回結果,判斷是否須要新建線程。
JDK 原生隊列類型 LinkedBlockingQueue
, SynchronousQueue
,二者實現邏輯不盡相同。
LinkedBlockingQueue
offer
方法內部將會根據隊列是否已滿做爲判斷條件。若隊列已滿,返回 false
,若隊列未滿,則將任務加入隊列中,且返回 true
。
SynchronousQueue
這個隊列比較特殊,內部不會儲存任何數據。如有線程將任務放入其中將會被阻塞,直到其餘線程將任務取出。反之,若無其餘線程將任務放入其中,該隊列取任務的方法也將會被阻塞,直到其餘線程將任務放入。
對於 offer
方法來講,如有其餘線程正在被取方法阻塞,該方法將會返回 true
。反之,offer 方法將會返回 false。
因此若想實現適合 io 密集型任務線程池,即優先新建線程處理任務,關鍵在於 queue#offer
方法。能夠重寫該方法內部邏輯,只要當前線程池數量小於最大線程數,該方法返回 false
,線程池新建線程處理。
固然上述實現邏輯比較糙,下面咱們就從 Tomcat 源碼查看其實現邏輯。
Tomcat 擴展線程池直接繼承 JDK 線程池 java.util.concurrent.ThreadPoolExecutor
,重寫部分方法的邏輯。另外還實現了 TaskQueue
,直接繼承 LinkedBlockingQueue
,重寫 offer
方法。
首先查看 Tomcat 線程池的使用方法。
能夠看到 Tomcat 線程池使用方法與普通的線程池差不太多。
接着咱們查看一下 Tomcat 線程池核心方法 execute
的邏輯。
execute
方法邏輯比較簡單,任務核心仍是交給 Java 原生線程池處理。這裏主要增長一個重試策略,若是原生線程池執行拒絕策略的狀況,拋出 RejectedExecutionException
異常。這裏將會捕獲,而後從新再次嘗試將任務加入到 TaskQueue
,盡最大可能執行任務。
這裏須要注意 submittedCount
變量。這是 Tomcat 線程池內部一個重要的參數,它是一個 AtomicInteger
變量,將會實時統計已經提交到線程池中,但尚未執行結束的任務。也就是說 submittedCount
等於線程池隊列中的任務數加上線程池工做線程正在執行的任務。 TaskQueue#offer
將會使用該參數實現相應的邏輯。
接着咱們主要查看 TaskQueue#offer
方法邏輯。
核心邏輯在於第三步,這裏若是 submittedCount
小於當前線程池線程數量,將會返回 false
。上面咱們講到 offer
方法返回 false
,線程池將會直接建立新線程。
Dubbo 2.6.X 版本增長 EagerThreadPool
,其實現原理與 Tomcat 線程池差很少,感興趣的小夥伴能夠自行翻閱。
上述擴展方法雖然看起不是很難,可是本身實現代價可能就比較大。若不想擴展線程池運行 io 密集型任務,能夠採用下面這種折衷方法。
new ThreadPoolExecutor(10, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(100));
複製代碼
不過使用這種方式將會使 keepAliveTime
失效,線程一旦被建立,將會一直存在,比較浪費系統資源。
JDK 實現線程池功能比較完善,可是比較適合運行 CPU 密集型任務,不適合 IO 密集型的任務。對於 IO 密集型任務能夠間接經過設置線程池參數方式作到。
歡迎關注個人公衆號:程序通事,得到平常乾貨推送。若是您對個人專題內容感興趣,也能夠關注個人博客:studyidea.cn