您好,登錄后才能下訂單哦!
本篇內(nèi)容主要講解“Tomcat怎么擴(kuò)展線程池”,感興趣的朋友不妨來看看。本文介紹的方法操作簡(jiǎn)單快捷,實(shí)用性強(qiáng)。下面就讓小編來帶大家學(xué)習(xí)“Tomcat怎么擴(kuò)展線程池”吧!
JDK 線程池
通常我們可以將執(zhí)行的任務(wù)分為兩類:
cpu 密集型任務(wù)
io 密集型任務(wù)
cpu 密集型任務(wù),需要線程長(zhǎng)時(shí)間進(jìn)行的復(fù)雜的運(yùn)算,這種類型的任務(wù)需要少創(chuàng)建線程,過多的線程將會(huì)頻繁引起上文切換,降低任務(wù)處理處理速度。
而 io 密集型任務(wù),由于線程并不是一直在運(yùn)行,可能大部分時(shí)間在等待 IO 讀取/寫入數(shù)據(jù),增加線程數(shù)量可以提高并發(fā)度,盡可能多處理任務(wù)。
JDK 原生線程池工作流程如下:
線程池執(zhí)行流程圖
“詳情可以查看 一文教你安全的關(guān)閉線程池, 上圖假設(shè)使用 LinkedBlockingQueue。
“靈魂拷問:上述流程是否記錯(cuò)過?在很長(zhǎng)一段時(shí)間內(nèi),我都認(rèn)為線程數(shù)量到達(dá)最大線程數(shù),才放入隊(duì)列中。 ̄□ ̄||
上圖中可以發(fā)現(xiàn)只要線程池線程數(shù)量大于核心線程數(shù),就會(huì)先將任務(wù)加入到任務(wù)隊(duì)列中,只有任務(wù)隊(duì)列加入失敗,才會(huì)再新建線程。也就是說原生線程池隊(duì)列未滿之前,最多只有核心線程數(shù)量線程。
這種策略顯然比較適合處理 cpu 密集型任務(wù),但是對(duì)于 io 密集型任務(wù),如數(shù)據(jù)庫查詢,rpc 請(qǐng)求調(diào)用等,就不是很友好了。
由于 Tomcat/Jetty 需要處理大量客戶端請(qǐng)求任務(wù),如果采用原生線程池,一旦接受請(qǐng)求數(shù)量大于線程池核心線程數(shù),這些請(qǐng)求就會(huì)被放入到隊(duì)列中,等待核心線程處理。這樣做顯然降低這些請(qǐng)求總體處理速度,所以兩者都沒采用 JDK 原生線程池。
解決上面的辦法可以像 Jetty 自己實(shí)現(xiàn)線程池組件,這樣就可以更加適配內(nèi)部邏輯,不過開發(fā)難度比較大,另一種就像 Tomcat 一樣,擴(kuò)展原生 JDK 線程池,實(shí)現(xiàn)比較簡(jiǎn)單。
下面主要以 Tomcat 擴(kuò)展線程池,講講其實(shí)現(xiàn)原理。
擴(kuò)展線程池
首先我們從 JDK 線程池源碼出發(fā),查看如何這個(gè)基礎(chǔ)上擴(kuò)展。
可以看到線程池流程主要分為三步,第二步根據(jù) queue#offer 方法返回結(jié)果,判斷是否需要新建線程。
JDK 原生隊(duì)列類型 LinkedBlockingQueue, SynchronousQueue,兩者實(shí)現(xiàn)邏輯不盡相同。
LinkedBlockingQueue
offer 方法內(nèi)部將會(huì)根據(jù)隊(duì)列是否已滿作為判斷條件。若隊(duì)列已滿,返回 false,若隊(duì)列未滿,則將任務(wù)加入隊(duì)列中,且返回 true。
SynchronousQueue
這個(gè)隊(duì)列比較特殊,內(nèi)部不會(huì)儲(chǔ)存任何數(shù)據(jù)。若有線程將任務(wù)放入其中將會(huì)被阻塞,直到其他線程將任務(wù)取出。反之,若無其他線程將任務(wù)放入其中,該隊(duì)列取任務(wù)的方法也將會(huì)被阻塞,直到其他線程將任務(wù)放入。
對(duì)于 offer 方法來說,若有其他線程正在被取方法阻塞,該方法將會(huì)返回 true。反之,offer 方法將會(huì)返回 false。
所以若想實(shí)現(xiàn)適合 io 密集型任務(wù)線程池,即優(yōu)先新建線程處理任務(wù),關(guān)鍵在于 queue#offer 方法。可以重寫該方法內(nèi)部邏輯,只要當(dāng)前線程池?cái)?shù)量小于最大線程數(shù),該方法返回false,線程池新建線程處理。
當(dāng)然上述實(shí)現(xiàn)邏輯比較糙,下面我們就從 Tomcat 源碼查看其實(shí)現(xiàn)邏輯。
Tomcat 擴(kuò)展線程池
Tomcat 擴(kuò)展線程池直接繼承 JDK 線程池 java.util.concurrent.ThreadPoolExecutor,重寫部分方法的邏輯。另外還實(shí)現(xiàn)了 TaskQueue,直接繼承 LinkedBlockingQueue,重寫 offer 方法。
首先查看 Tomcat 線程池的使用方法。
可以看到 Tomcat 線程池使用方法與普通的線程池差不太多。
接著我們查看一下 Tomcat 線程池核心方法 execute 的邏輯。
execute 方法邏輯比較簡(jiǎn)單,任務(wù)核心還是交給 Java 原生線程池處理。這里主要增加一個(gè)重試策略,如果原生線程池執(zhí)行拒絕策略的情況,拋出 RejectedExecutionException 異常。這里將會(huì)捕獲,然后重新再次嘗試將任務(wù)加入到 TaskQueue ,盡最大可能執(zhí)行任務(wù)。
這里需要注意 submittedCount 變量。這是 Tomcat 線程池內(nèi)部一個(gè)重要的參數(shù),它是一個(gè) AtomicInteger 變量,將會(huì)實(shí)時(shí)統(tǒng)計(jì)已經(jīng)提交到線程池中,但還沒有執(zhí)行結(jié)束的任務(wù)。也就是說 submittedCount 等于線程池隊(duì)列中的任務(wù)數(shù)加上線程池工作線程正在執(zhí)行的任務(wù)。TaskQueue#offer 將會(huì)使用該參數(shù)實(shí)現(xiàn)相應(yīng)的邏輯。
接著我們主要查看 TaskQueue#offer 方法邏輯。
核心邏輯在于第三步,這里如果 submittedCount 小于當(dāng)前線程池線程數(shù)量,將會(huì)返回false。上面我們講到 offer 方法返回 false,線程池將會(huì)直接創(chuàng)建新線程。
Dubbo 2.6.X 版本增加 EagerThreadPool,其實(shí)現(xiàn)原理與 Tomcat 線程池差不多,感興趣的小伙伴可以自行翻閱。
折衷方法
上述擴(kuò)展方法雖然看起不是很難,但是自己實(shí)現(xiàn)代價(jià)可能就比較大。若不想擴(kuò)展線程池運(yùn)行 io 密集型任務(wù),可以采用下面這種折衷方法。
new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue
不過使用這種方式將會(huì)使 keepAliveTime 失效,線程一旦被創(chuàng)建,將會(huì)一直存在,比較浪費(fèi)系統(tǒng)資源。
到此,相信大家對(duì)“Tomcat怎么擴(kuò)展線程池”有了更深的了解,不妨來實(shí)際操作一番吧!這里是億速云網(wǎng)站,更多相關(guān)內(nèi)容可以進(jìn)入相關(guān)頻道進(jìn)行查詢,關(guān)注我們,繼續(xù)學(xué)習(xí)!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。