日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

原生线程池这么强大,Tomcat 为何还需扩展线程池?

發(fā)布時間:2025/3/16 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 原生线程池这么强大,Tomcat 为何还需扩展线程池? 小編覺得挺不錯的,現(xiàn)在分享給大家,幫大家做個參考.

前言

Tomcat/Jetty 是目前比較流行的 Web 容器,兩者接受請求之后都會轉(zhuǎn)交給線程池處理,這樣可以有效提高處理的能力與并發(fā)度。JDK 提高完整線程池實現(xiàn),但是 Tomcat/Jetty 都沒有直接使用。

Jetty 采用自研方案,內(nèi)部實現(xiàn) QueuedThreadPool 線程池組件,而 Tomcat 采用擴展方案,踩在 JDK 線程池的肩膀上,擴展 JDK 原生線程池。

JDK 原生線程池可以說功能比較完善,使用也比較簡單,那為何 Tomcat/Jetty 卻不選擇這個方案,反而自己去動手實現(xiàn)那?


JDK 線程池

通常我們可以將執(zhí)行的任務分為兩類:

  • cpu 密集型任務

  • io 密集型任務

cpu 密集型任務,需要線程長時間進行的復雜的運算,這種類型的任務需要少創(chuàng)建線程,過多的線程將會頻繁引起上文切換,降低任務處理處理速度。

而 io 密集型任務,由于線程并不是一直在運行,可能大部分時間在等待 IO 讀取/寫入數(shù)據(jù),增加線程數(shù)量可以提高并發(fā)度,盡可能多處理任務。

JDK 原生線程池工作流程如下:

線程池執(zhí)行流程圖

詳情可以查看 一文教你安全的關(guān)閉線程池, 上圖假設使用 LinkedBlockingQueue。

靈魂拷問:上述流程是否記錯過?在很長一段時間內(nèi),我都認為線程數(shù)量到達最大線程數(shù),才放入隊列中。 ̄□ ̄||

上圖中可以發(fā)現(xiàn)只要線程池線程數(shù)量大于核心線程數(shù),就會先將任務加入到任務隊列中,只有任務隊列加入失敗,才會再新建線程。也就是說原生線程池隊列未滿之前,最多只有核心線程數(shù)量線程。

這種策略顯然比較適合處理 cpu 密集型任務,但是對于 io 密集型任務,如數(shù)據(jù)庫查詢,rpc 請求調(diào)用等,就不是很友好了。

由于 Tomcat/Jetty 需要處理大量客戶端請求任務,如果采用原生線程池,一旦接受請求數(shù)量大于線程池核心線程數(shù),這些請求就會被放入到隊列中,等待核心線程處理。這樣做顯然降低這些請求總體處理速度,所以兩者都沒采用 JDK 原生線程池。

解決上面的辦法可以像 Jetty 自己實現(xiàn)線程池組件,這樣就可以更加適配內(nèi)部邏輯,不過開發(fā)難度比較大,另一種就像 Tomcat 一樣,擴展原生 JDK 線程池,實現(xiàn)比較簡單。

下面主要以 Tomcat 擴展線程池,講講其實現(xiàn)原理。


擴展線程池

首先我們從 JDK 線程池源碼出發(fā),查看如何這個基礎上擴展。

可以看到線程池流程主要分為三步,第二步根據(jù) queue#offer 方法返回結(jié)果,判斷是否需要新建線程。

JDK 原生隊列類型 LinkedBlockingQueue, ?SynchronousQueue,兩者實現(xiàn)邏輯不盡相同。

LinkedBlockingQueue

offer 方法內(nèi)部將會根據(jù)隊列是否已滿作為判斷條件。若隊列已滿,返回 false,若隊列未滿,則將任務加入隊列中,且返回 true。

SynchronousQueue

這個隊列比較特殊,內(nèi)部不會儲存任何數(shù)據(jù)。若有線程將任務放入其中將會被阻塞,直到其他線程將任務取出。反之,若無其他線程將任務放入其中,該隊列取任務的方法也將會被阻塞,直到其他線程將任務放入。

對于 offer 方法來說,若有其他線程正在被取方法阻塞,該方法將會返回 true。反之,offer 方法將會返回 false。

所以若想實現(xiàn)適合 io 密集型任務線程池,即優(yōu)先新建線程處理任務,關(guān)鍵在于 queue#offer ?方法。可以重寫該方法內(nèi)部邏輯,只要當前線程池數(shù)量小于最大線程數(shù),該方法返回 false,線程池新建線程處理。

當然上述實現(xiàn)邏輯比較糙,下面我們就從 Tomcat 源碼查看其實現(xiàn)邏輯。


Tomcat 擴展線程池

Tomcat 擴展線程池直接繼承 JDK 線程池 java.util.concurrent.ThreadPoolExecutor,重寫部分方法的邏輯。另外還實現(xiàn)了 TaskQueue,直接繼承 LinkedBlockingQueue,重寫 offer ?方法。

首先查看 Tomcat 線程池的使用方法。

可以看到 Tomcat 線程池使用方法與普通的線程池差不太多。

接著我們查看一下 Tomcat 線程池核心方法 execute ?的邏輯。

execute 方法邏輯比較簡單,任務核心還是交給 Java 原生線程池處理。這里主要增加一個重試策略,如果原生線程池執(zhí)行拒絕策略的情況,拋出 RejectedExecutionException 異常。這里將會捕獲,然后重新再次嘗試將任務加入到 TaskQueue ,盡最大可能執(zhí)行任務。

這里需要注意 submittedCount 變量。這是 Tomcat 線程池內(nèi)部一個重要的參數(shù),它是一個 AtomicInteger 變量,將會實時統(tǒng)計已經(jīng)提交到線程池中,但還沒有執(zhí)行結(jié)束的任務。也就是說 submittedCount 等于線程池隊列中的任務數(shù)加上線程池工作線程正在執(zhí)行的任務。TaskQueue#offer 將會使用該參數(shù)實現(xiàn)相應的邏輯。

接著我們主要查看 TaskQueue#offer 方法邏輯。

核心邏輯在于第三步,這里如果 submittedCount 小于當前線程池線程數(shù)量,將會返回 false。上面我們講到 offer 方法返回 false,線程池將會直接創(chuàng)建新線程。

Dubbo 2.6.X 版本增加 EagerThreadPool,其實現(xiàn)原理與 Tomcat 線程池差不多,感興趣的小伙伴可以自行翻閱。


折衷方法

上述擴展方法雖然看起不是很難,但是自己實現(xiàn)代價可能就比較大。若不想擴展線程池運行 io 密集型任務,可以采用下面這種折衷方法。

new ThreadPoolExecutor(10, 10,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>(100));

不過使用這種方式將會使 keepAliveTime 失效,線程一旦被創(chuàng)建,將會一直存在,比較浪費系統(tǒng)資源。

總結(jié)

JDK 實現(xiàn)線程池功能比較完善,但是比較適合運行 CPU 密集型任務,不適合 IO 密集型的任務。對于 IO 密集型任務可以間接通過設置線程池參數(shù)方式做到。

有道無術(shù),術(shù)可成;有術(shù)無道,止于術(shù)

歡迎大家關(guān)注Java之道公眾號

好文章,我在看??

總結(jié)

以上是生活随笔為你收集整理的原生线程池这么强大,Tomcat 为何还需扩展线程池?的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯,歡迎將生活随笔推薦給好友。