<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          原生線程池這么強(qiáng)大,Tomcat 為何還需擴(kuò)展線程池?

          共 2579字,需瀏覽 6分鐘

           ·

          2020-01-15 23:27

          前言

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

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

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


          JDK 線程池

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

          • cpu 密集型任務(wù)

          • io 密集型任務(wù)

          cpu 密集型任務(wù),需要線程長(zhǎng)時(shí)間進(jìn)行的復(fù)雜的運(yùn)算,這種類型的任務(wù)需要少創(chuàng)建線程,過(guò)多的線程將會(huì)頻繁引起上文切換,降低任務(wù)處理處理速度。

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

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

          195c0628aad7f60ed9793532e39848f8.webp

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

          詳情可以查看 一文教你安全的關(guān)閉線程池, 上圖假設(shè)使用 LinkedBlockingQueue
          靈魂拷問(wèn):上述流程是否記錯(cuò)過(guò)?在很長(zhǎng)一段時(shí)間內(nèi),我都認(rèn)為線程數(shù)量到達(dá)最大線程數(shù),才放入隊(duì)列中。 ̄□ ̄||
          上圖中可以發(fā)現(xiàn)只要線程池線程數(shù)量大于核心線程數(shù),就會(huì)先將任務(wù)加入到任務(wù)隊(duì)列中,只有任務(wù)隊(duì)列加入失敗,才會(huì)再新建線程。也就是說(shuō)原生線程池隊(duì)列未滿之前,最多只有核心線程數(shù)量線程。這種策略顯然比較適合處理 cpu 密集型任務(wù),但是對(duì)于 io 密集型任務(wù),如數(shù)據(jù)庫(kù)查詢,rpc 請(qǐng)求調(diào)用等,就不是很友好了。由于 Tomcat/Jetty 需要處理大量客戶端請(qǐng)求任務(wù),如果采用原生線程池,一旦接受請(qǐng)求數(shù)量大于線程池核心線程數(shù),這些請(qǐng)求就會(huì)被放入到隊(duì)列中,等待核心線程處理。這樣做顯然降低這些請(qǐng)求總體處理速度,所以兩者都沒(méi)采用 JDK 原生線程池。解決上面的辦法可以像 Jetty 自己實(shí)現(xiàn)線程池組件,這樣就可以更加適配內(nèi)部邏輯,不過(guò)開(kāi)發(fā)難度比較大,另一種就像 Tomcat 一樣,擴(kuò)展原生 JDK 線程池,實(shí)現(xiàn)比較簡(jiǎn)單。下面主要以 Tomcat 擴(kuò)展線程池,講講其實(shí)現(xiàn)原理。


          擴(kuò)展線程池

          首先我們從 JDK 線程池源碼出發(fā),查看如何這個(gè)基礎(chǔ)上擴(kuò)展。bde53e35c8bd40d59afb9c4e120af7f7.webp可以看到線程池流程主要分為三步,第二步根據(jù) queue#offer 方法返回結(jié)果,判斷是否需要新建線程。JDK 原生隊(duì)列類型 LinkedBlockingQueue, ?SynchronousQueue,兩者實(shí)現(xiàn)邏輯不盡相同。

          LinkedBlockingQueue

          offer 方法內(nèi)部將會(huì)根據(jù)隊(duì)列是否已滿作為判斷條件。若隊(duì)列已滿,返回 false,若隊(duì)列未滿,則將任務(wù)加入隊(duì)列中,且返回 trueSynchronousQueue這個(gè)隊(duì)列比較特殊,內(nèi)部不會(huì)儲(chǔ)存任何數(shù)據(jù)。若有線程將任務(wù)放入其中將會(huì)被阻塞,直到其他線程將任務(wù)取出。反之,若無(wú)其他線程將任務(wù)放入其中,該隊(duì)列取任務(wù)的方法也將會(huì)被阻塞,直到其他線程將任務(wù)放入。對(duì)于 offer 方法來(lái)說(shuō),若有其他線程正在被取方法阻塞,該方法將會(huì)返回 true反之,offer 方法將會(huì)返回 false。所以若想實(shí)現(xiàn)適合 io 密集型任務(wù)線程池,即優(yōu)先新建線程處理任務(wù),關(guān)鍵在于 queue#offer ?方法。可以重寫(xiě)該方法內(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,重寫(xiě)部分方法的邏輯。另外還實(shí)現(xiàn)了 TaskQueue,直接繼承 LinkedBlockingQueue,重寫(xiě) offer ?方法。首先查看 Tomcat 線程池的使用方法。7e10b3dfc1e78c2d87afca7ecbff8325.webp可以看到 Tomcat 線程池使用方法與普通的線程池差不太多。接著我們查看一下 Tomcat 線程池核心方法 execute ?的邏輯。db17e53b0fc4e96735b545ba9ee2247f.webpexecute 方法邏輯比較簡(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)提交到線程池中,但還沒(méi)有執(zhí)行結(jié)束的任務(wù)。也就是說(shuō) submittedCount 等于線程池隊(duì)列中的任務(wù)數(shù)加上線程池工作線程正在執(zhí)行的任務(wù)。TaskQueue#offer 將會(huì)使用該參數(shù)實(shí)現(xiàn)相應(yīng)的邏輯。接著我們主要查看 TaskQueue#offer 方法邏輯。b0e98048e18fc0d1b8bf6f97b3ca79fd.webp核心邏輯在于第三步,這里如果 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(100));
          不過(guò)使用這種方式將會(huì)使 keepAliveTime 失效,線程一旦被創(chuàng)建,將會(huì)一直存在,比較浪費(fèi)系統(tǒng)資源。

          總結(jié)

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

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

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


          好文章,我在看??

          瀏覽 40
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美专区在线观看 | 国产精品一卡二卡在线观看 | 久久好色| 一区二区三区四区五区六区七区 | 精品欧美操屄网 |