線程池帶來的好處
- 第一:降低資源消耗姚建。通過重復利用已創(chuàng)建的線程降低線程創(chuàng)建和銷毀造成的消耗。
- 第二:提高響應速度。當任務到達時埠褪,任務可以不需要等到線程創(chuàng)建就能立即執(zhí)行串述。
- 第三:提高線程的可管理性画畅。線程是稀缺資源谬盐,如果無限制地創(chuàng)建炕置,不僅會消耗系統(tǒng)資源兆沙,還會降低系統(tǒng)的穩(wěn)定性欧芽,使用線程池可以進行統(tǒng)一分配、調優(yōu)和監(jiān)控葛圃。但是千扔,要做到合理利用線程池,必須對其實現原理了如指掌库正。
線程池的實現原理
-
當向線程池提交一個任務之后曲楚,線程池是如何處理這個任務的呢?
1)線程池判斷核心線程池里的線程是否都在執(zhí)行任務褥符。如果不是龙誊,則創(chuàng)建一個新的工作線程來執(zhí)行任務。如果核心線程池里的線程都在執(zhí)行任務喷楣,則進入下個流程趟大。
2)線程池判斷工作隊列是否已經滿鹤树。如果工作隊列沒有滿,逊朽,則將新提交的任務存儲在 這個工作隊列里罕伯。如果工作隊列滿了,則進入下個流程叽讳。
3)線程池判斷線程池的線程是否都處于工作狀態(tài)追他。如果沒有,則創(chuàng)建一個新的工作線程來執(zhí)行任務岛蚤。如果已經滿了邑狸,則交給飽和策略來處理這個任務
線程池的主要處理流程 -
ThreadPoolExecutor 執(zhí)行 execute() 方法時的流程如下:
1)如果當前運行的線程少于
corePoolSize
,則創(chuàng)建新線程來執(zhí)行任務(注意灭美,執(zhí)行這一步驟需要獲取全局鎖)推溃。2)如果運行的線程等于或多于
corePoolSize
,則將任務加入BlockingQueue
(隊列已滿)届腐,則創(chuàng)建新的線程來處理任務(注意,執(zhí)行這一步驟需要獲取全局鎖)蜂奸。-
4)如果創(chuàng)建新線程將使當前運行的線程超出
maximumPoolSize
犁苏,任務將被拒絕,并調用RejectedExecutionHandler.rejectedExecution()
方法ThreadPoolExecutor執(zhí)行圖
線程池的使用
線程池的創(chuàng)建
-
我們可以通過 ThreadPoolExecutor 來創(chuàng)建一個線程池:
new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
-
創(chuàng)建一個線程池時需要輸入幾個參數扩所,如下:
1)
corePoolSize
(線程池的基本大形辍):當提交一個任務到線程池時,線程池會創(chuàng)建一個線程來執(zhí)行任務祖屏,即使其他空閑的基本線程能夠執(zhí)行新任務也會創(chuàng)建線程助赞,等到需要執(zhí)行的任務數大于線程池基本大小時就不再創(chuàng)建。如果調用了線程池的 prestartAllCoreThreads() 方法袁勺,線程池會提前創(chuàng)建并啟動所有基本線程雹食。-
2)
runnableTaskQueue
(任務隊列):用于保存等待執(zhí)行的任務的阻塞隊列∑诜幔可以選擇以下幾個阻塞隊列-
ArrayBlockingQueue
:是一個基于數組結構的有界阻塞隊列群叶,此隊列按 FIFO(先進先出)原則對元素進行排序。 -
LinkedBlockingQueue
:一個基于鏈表結構的阻塞隊列钝荡,此隊列按 FIFO 排序元素街立,吞吐量通常要高于 ArrayBlockingQueue。靜態(tài)工廠方法 Executors.newFixedThreadPool() 使用了這個隊列 -
SynchronousQueue
:一個不存儲元素的阻塞隊列埠通。每個插入操作必須等到另一個 線程調用移除操作赎离,否則插入操作一直處于阻塞狀態(tài),吞吐量通常要高于LinkedBlockingQueue
端辱,靜態(tài)工廠方法Executors.newCachedThreadPool
使用了這個隊列梁剔。 -
PriorityBlockingQueue
:一個具有優(yōu)先級的無限阻塞隊列
-
3)
maximumPoolSize
(線程池最大數量):線程池允許創(chuàng)建的最大線程數虽画。如果隊列滿了,并且已創(chuàng)建的線程數小于最大線程數憾朴,則線程池會再創(chuàng)建新的線程執(zhí)行任務狸捕。值得注意的是,如果使用了無界的任務隊列這個參數就沒什么效果(無界隊列不會滿)众雷。-
4)
ThreadFactory
:用于設置創(chuàng)建線程的工廠灸拍,可以通過線程工廠給每個創(chuàng)建出來的線程設置更有意義的名字。使用開源框架 guava 提供的 ThreadFactoryBuilder 可以快速給線程池里的線程設置有意義的名字砾省,代碼如下:-
new ThreadFactoryBuilder().setNameFormat("XX-task-%d").build()
;
-
-
5)
RejectedExecutionHandler
(飽和策略):當隊列和線程池都滿了鸡岗,說明線程池處于飽和狀態(tài),那么必須采取一種策略處理提交的新任務编兄。這個策略默認情況下是AbortPolicy
轩性,表示無法處理新任務時拋出異常。在 JDK 1.5 中 Java 線程池框架提供了以下 4 種策略狠鸳。-
AbortPolicy
:直接拋出異常 -
CallerRunsPolicy
:只用調用者所在線程來運行任務揣苏。 -
DiscardOldestPolicy
:丟棄隊列里最近的一個任務,并執(zhí)行當前任務件舵。 -
DiscardPolicy
:不處理卸察,丟棄掉。 - 當然铅祸,也可以根據應用場景需要來實現 RejectedExecutionHandler 接口自定義策略坑质。如記錄日志或持久化存儲不能處理的任務。
-
6)
keepAliveTime
(線程活動保持時間):線程池的工作線程空閑后临梗,保持存活的時間涡扼。所以,如果任務很多盟庞,并且每個任務執(zhí)行的時間比較短吃沪,可以調大時間,提高線程的利用率茫经。7)
TimeUnit
(線程活動保持時間的單位):可選的單位有天(DAYS)巷波、小時(HOURS)、分鐘(MINUTES)卸伞、毫秒(MILLISECONDS)抹镊、微秒(MICROSECONDS,千分之一毫秒)和納秒(NANOSECONDS荤傲,千分之一微秒)垮耳。
向線程池提交任務
- 可以使用兩個方法向線程池提交任務,分別為 execute() 和 submit() 方法。
- execute() 方法輸入的任務是一個 Runnable 類的實例终佛。
void execute(Runnable command)
- submit() 方法輸入任務可以是 Runnable 也可以是 Callable 的示例:
-
Future<?> submit(Runnable task);
俊嗽,返回的future 調用 get 方法時,返回的結果 null -
<T> Future<T> submit(Runnable task, T result);
铃彰,返回的future 調用 get 方法時绍豁,返回的結果是 result -
<T> Future<T> submit(Callable<T> task);
, 返回的 future 調用get方法時, 是 Callable 實例中 call 方法返回的結果
-
- execute() 方法輸入的任務是一個 Runnable 類的實例终佛。
關閉線程
- 可以通過調用線程池的 shutdown 或 shutdownNow 方法來關閉線程池牙捉。它們的原理是遍歷線程池中的工作線程竹揍,然后逐個調用線程的 interrupt 方法來中斷線程,所以無法響應中斷的任務可能永遠無法終止邪铲。
- 但是它們存在一定的區(qū)別芬位,shutdownNow 首先將線程池的狀態(tài)設置成 STOP,然后嘗試停止所有的正在執(zhí)行或暫停任務的線程带到,并返回等待執(zhí)行任務的列表昧碉,
- 而 shutdown 只是將線程池的狀態(tài)設置SHUTDOWN 狀態(tài),然后中斷所有沒有正在執(zhí)行任務的線程
合理地配置線程池
- 分析的角度:
- 任務的性質:CPU 密集型任務揽惹、IO 密集型任務和混合型任務被饿。
- 任務的優(yōu)先級:高、中和低搪搏。
-
性質不同的任務可以用不同規(guī)模的線程池分開處理
- CPU 密集型任務應配置盡可能小的線程锹漱,如配置 Ncpu+1 個線程的線程池。
- 由于 IO 密集型任務線程并不是一直在執(zhí)行任務慕嚷,則應配置盡可能多的線程,如 2*Ncpu毕泌。
- 混合型的任務喝检,如果可以拆分,將其拆分成一個 CPU 密集型任務和一個 IO 密集型任務撼泛,只要這兩個任務執(zhí)行的時間相差不是太大挠说,那么分解后執(zhí)行的吞吐量將高于串行執(zhí)行的吞吐量。如果這兩個任務執(zhí)行時間相差太大愿题,則沒必要進行分解损俭。
- 可以通過 Runtime.getRuntime().availableProcessors() 方法獲得當前設備的 CPU 個數。
- 優(yōu)先級不同的任務可以使用優(yōu)先級隊列 PriorityBlockingQueue 來處理潘酗。它可以讓優(yōu)先級
高的任務先執(zhí)行杆兵。 - 執(zhí)行時間不同的任務可以交給不同規(guī)模的線程池來處理,或者可以使用優(yōu)先級隊列仔夺,讓執(zhí)行時間短的任務先執(zhí)行琐脏。
- 依賴數據庫連接池的任務,因為線程提交 SQL 后需要等待數據庫返回結果,等待的時間越長日裙,則 CPU 空閑時間就越長吹艇,那么線程數應該設置得越大,這樣才能更好地利用 CPU昂拂。
- 建議使用有界隊列受神。有界隊列能增加系統(tǒng)的穩(wěn)定性和預警能力,可以根據需要設大一點兒格侯,比如幾千鼻听。
參考 《Java并發(fā)編程藝術》、《Java并發(fā)編程實戰(zhàn)》