原生執行緒池這麼強大,Tomcat 為何還需擴充套件執行緒池?
前言
Tomcat/Jetty 是目前比較流行的 Web 容器,兩者接受請求之後都會轉交給執行緒池處理,這樣可以有效提高處理的能力與併發度。JDK 提高完整執行緒池實現,但是 Tomcat/Jetty 都沒有直接使用。Jetty 採用自研方案,內部實現 QueuedThreadPool
執行緒池元件,而 Tomcat 採用擴充套件方案,踩在 JDK 執行緒池的肩膀上,擴充套件 JDK 原生執行緒池。
JDK 原生執行緒池可以說功能比較完善,使用也比較簡單,那為何 Tomcat/Jetty 卻不選擇這個方案,反而自己去動手實現那?
JDK 執行緒池
通常我們可以將執行的任務分為兩類:
- cpu 密集型任務
- io 密集型任務
cpu 密集型任務,需要執行緒長時間進行的複雜的運算,這種型別的任務需要少建立執行緒,過多的執行緒將會頻繁引起上文切換,降低任務處理處理速度。
而 io 密集型任務,由於執行緒並不是一直在執行,可能大部分時間在等待 IO 讀取/寫入資料,增加執行緒數量可以提高併發度,儘可能多處理任務。
JDK 原生執行緒池工作流程如下:
詳情可以檢視 一文教你安全的關閉執行緒池,上圖假設使用
LinkedBlockingQueue
。
靈魂拷問:上述流程是否記錯過?在很長一段時間內,我都認為執行緒數量到達最大執行緒數,才放入佇列中。 ̄□ ̄||
上圖中可以發現只要執行緒池執行緒數量大於核心執行緒數,就會先將任務加入到任務佇列中,只有任務佇列加入失敗,才會再新建執行緒。也就是說原生執行緒池佇列未滿之前,最多隻有核心執行緒數量執行緒。
這種策略顯然比較適合處理 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 擴充套件執行緒池
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
相關推薦
原生執行緒池這麼強大,Tomcat 為何還需擴充套件執行緒池?
前言 Tomcat/Jetty 是目前比較流行的 Web 容器,兩者接受請求之後都會轉交給執行緒池處理,這樣可以有效提高處理的能力與併發度。JDK 提高完整執行緒池實現,但是 Tomcat/Jetty 都沒有直接使用。Jetty 採用自研方案,內部實現 QueuedThreadPool 執行緒池元件,而 To
執行緒池大小設定,CPU的核心數、執行緒數的關係和區別,同步與堵塞完全是兩碼事
執行緒池應該設定多少執行緒合適,怎麼樣估算出來。最近接觸到一些相關資料,現作如下總結。 最開始接觸執行緒池的時候,沒有想到就僅僅是設定一個執行緒池的大小居然還有這麼多的學問,汗顏啊。 首先,需要考慮到執行緒池所進行的工作的性質: IO密集型 CPU密集型 簡單的分析來看,如果是CPU密集
有三個執行緒T1 T2 T3,如何保證他們按順序執行
T3先執行,在T3的run中,呼叫t2.join,讓t2執行完成後再執行t3 在T2的run中,呼叫t1.join,讓t1執行完成後再讓T2執行 public class JoinTest2 { // 1.現在有T1、T2、T3三個執行緒,你怎樣保證T2在T1
robotframework 判斷下拉框是否存在,如果存在就執行下拉框操作,不存在就跳過執行下拉框操作,進行下一步操作;
新頁面 存在 robot sel log 是否 work val image #本人新手,僅做學習記錄之用 因為工作要求,打開的ui頁面,根據前面篩選的條件不同,跳轉的新頁面不同,本記錄涉及的就是有下拉框和沒有下拉框,所以要對新打開的頁面進行判斷;run keyword
亞馬遜AWS寧夏資料中心早就完工了,為何還不投入執行?
左一為前美國白宮發言人、現任亞馬遜全球高階副總裁傑伊·卡尼(Jay Carney) 誰是獨角獸 發自美國西雅圖 5年甚至更長,是亞馬遜在寧夏中衛資料中心落地的時間。 美國當地時間5月9日,亞馬遜全球高階副總裁傑伊·卡尼(Jay Carney)在接受《誰是獨角獸》採訪時表示,亞馬遜雲端計算服務(Am
Java異常處理中try{}catch丟擲異常,後面程式碼還會繼續執行麼?
這張圖片上面顯示的程式碼執行之後將會輸出什麼?我們可以發現在procedure()函式結束之後函式後面的內容就不運行了,而主函式裡面的程式還是會繼續執行。反過來再測試如果先發生主函式裡面的異常那麼Pr
Coinness分析:BTC護盤止跌,但反彈還需時間
通過連日來的橫盤,各項指標已經逐步修復完成,現在只等時機成熟,變會出現變盤。 自下跌以來,已經連續震盪了15個交易日,訊息面方面的利好也較少,反而是利空的訊息接連不斷。所有人對ETF的期待都非常高,導致在結果出來之前,衝刺了一波,隨著ETF的被拒,BTC價格隨之回落。但收盤價並未低於6400美元
STM32 IAP韌體更新,bootloader起始地址偏移後,程式碼中還需設定中斷向量的偏移。
在 stm32f10x_flash.icf 中設定ROM的起始結束地址 /*-Specials-*/ define symbol __ICFEDIT_intvec_start__ = 0x08004000; /*-Memory Regions-*/ define symbo
成為一名軟體測試工程師必備的技能,除了技術還需天賦。。。
通用技能上: 1.基本計算機知識(作業系統,資料庫,通訊協議原理,熟悉至少一門程式語言) 2.基本軟體測試知識(各種測試理論,測試方法論,測試用例編寫,缺陷界定標準,軟體質量評估) 3.簡單專案管理知識 產品、系統認知: 1.熟悉所測產品功能,能夠將產品文件內描述的UC轉化成TC,這個最最基本 2.熟悉所測產
關於執行緒池工作原理,任務拒接策略有哪幾種
在ThreadPoolExecutor類中提供了四個構造方法: 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 public class ThreadPoolExecutor extends 
Java——多執行緒基本使用(四) 執行緒組和執行緒池的使用,工廠設計模式的使用
1.執行緒組的概述和使用 Java中使用ThreadGroup來表示執行緒組,它可以對一批執行緒進行分類管理,Java允許程式直接對執行緒組進行控制。 &n
執行緒池的好處,詳解,單例(絕對好記)
一、執行緒池的好處 執行緒池是啥子,幹啥使它呀,老子執行緒使得好好的,非得多次一舉,哈哈,想必來這裡看這篇文章的都對執行緒池有點了解。那麼我來整理整理執行緒池的好處吧。 1、執行緒池的重用 執行緒的建立和銷燬的開銷是巨大的,而通過執行緒池的重用大大減少了這些不必要的
tomcat優化後的worker執行緒池
tomcat實現了自己的worker執行緒池,重寫了ThreadPoolExecutor的execute部分邏輯,使之更適合web服務這種IO密集型任務。直接貼原始碼。 自定義的ThreadPoolExecutor: /** * Same as a java.util.
10.執行緒和執行緒池的區別,執行緒池有哪些,什麼情況下使用
一:執行緒和執行緒池的區別 (1)new Thread 的弊端 a. 每次new Thread時,新建物件效能差。 b. 執行緒缺乏統一管理,可能無限制新建執行緒,相互之間競爭,可能佔用過多系統資源導致宕機或oom。 c. 缺乏更多功能
Java多執行緒-----執行緒池的使用,原理以及舉例實現(三)(四):使用樣例及如何配置執行緒池大小
三.使用示例 前面我們討論了關於執行緒池的實現原理,這一節我們來看一下它的具體使用: public class Test { public static void main(String[] args) { ThreadPoolExe
詳解 Tomcat 的連線數與執行緒池
前言 在使用tomcat時,經常會遇到連線數、執行緒數之類的配置問題,要真正理解這些概念,必須先了解Tomcat的聯結器(Connector)。 在前面的文章 詳解Tomcat配置檔案server.xml 中寫到過:Connector的主要功能,是接收連線請求,建立R
Java執行緒池詳解,看這篇就夠了!
構造一個執行緒池為什麼需要幾個引數?如果避免執行緒池出現OOM?Runnable和Callable的區別是什麼?本文將對這些問題一一解答,同時還將給出使用執行緒池的常見場景和程式碼片段。 基礎知識 Executors建立執行緒池 Java中建立執行緒池很簡單,只需要呼叫Execu
執行緒池工具類,直接可用!
import android.support.annotation.IntDef; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; im
【本人禿頂程式設計師】Java執行緒池詳解,看這篇就夠了!
←←←←←←←←←←←← 快!點關注!!! 構造一個執行緒池為什麼需要幾個引數?如果避免執行緒池出現OOM?Runnable和Callable的區別是什麼?本文將對這些問題一一解答,同時還將給出使用執行緒池的常見場景和程式碼片段。 基礎知識 Executors建立執行緒池 J
Java併發程式設計:Java的四種執行緒池的使用,以及自定義執行緒工廠
目錄 引言 四種執行緒池 newCachedThreadPool:可快取的執行緒池 newFixedThreadPool:定長執行緒池 newSingleThreadExecutor:單執行緒執行緒池 newScheduledThreadPool:支援定時的定