便宜做網站的公司靠譜嗎國家免費職業(yè)技能培訓官網
文章目錄
- 一、為什么要用線程池
- 1.1 單線程的問題
- 1.2 手動創(chuàng)建多線程的問題
- 1.3 線程池的作用(優(yōu)點)
- 1.4 線程池的使用場景
- 二、線程池的基礎知識
- 2.1 線程池的核心組件
- 2.2 JUC中的線程池架構
- 2.3 線程池的配置參數
- 2.4 線程池常見的拒絕策略(可自定義)
- 2.5 線程池的生命周期
- 2.6 線程池的任務調度規(guī)則
- 2.7 線程池執(zhí)行器的鉤子方法
- 三、使用基礎
- 3.1 常見的工作隊列
- 3.2 Executors提供的線程池
- 3.3 使用Executors創(chuàng)建線程的問題
- 四、開發(fā)實踐
- 4.1 線程數量配置
- 4.2 提交任務:以submit(callable)為例
- 4.3 關閉線程池
- 參考
一、為什么要用線程池
1.1 單線程的問題
單線程無法利用CPU的多核資源,且存在阻塞問題。使用單個線程來處理一組任務,若當前任務阻塞了線程,該線程將失去CPU的使用權,而不是去執(zhí)行其他可以執(zhí)行的任務。
1.2 手動創(chuàng)建多線程的問題
需要開發(fā)人員去管理線程的生命周期,麻煩,且可能出錯。
還存在并發(fā)數不可控的問題,如每次到達一個任務時都新建一個線程來處理,當大量任務到達時,會創(chuàng)建大量的線程,導致線程間的競爭加劇,且可能導致系統(tǒng)資源耗盡。
1.3 線程池的作用(優(yōu)點)
- 節(jié)省線程創(chuàng)建和銷毀的開銷:線程池通過復用一組線程,可以節(jié)省頻繁創(chuàng)建和銷毀線程的開銷。
- 控制并發(fā)數:線程池可以控制最大并發(fā)數,避免線程過多導致系統(tǒng)資源耗盡。
- 提高響應速度:當任務到達時,可以直接使用線程池中已經創(chuàng)建好的線程,節(jié)省了新建線程的時間。
- 管理線程:線程池負責內部線程的生命周期管理,包括創(chuàng)建、銷毀和調度等,無需人工干預。
1.4 線程池的使用場景
- 處理并發(fā)請求:每個請求都要一個線程來處理,使用線程池來復用一組線程,能節(jié)省創(chuàng)建和銷毀線程的開銷。
- 執(zhí)行異步任務:避免耗時操作(如郵件發(fā)送、文件上傳)阻塞主線程,可以使用線程池來異步執(zhí)行這些任務。
- 處理IO密集型任務:線程在進行IO操作時會阻塞,使用線程池來調度這些任務,可以在任務阻塞時調度其他任務繼續(xù)執(zhí)行,避免CPU空閑。
- 處理計算密集型任務:使用線程池來執(zhí)行計算密集型任務,可以充分利用CPU的多核資源,也能控制并發(fā)數量避免系統(tǒng)資源耗盡。
- 后臺服務:利用線程池來執(zhí)行后臺服務,與前臺線程的執(zhí)行獨立,互不影響。
- 執(zhí)行定時任務與周期性任務:可以使用
ScheduledThreadPool
在后臺執(zhí)行這些任務,與前臺線程的執(zhí)行獨立。
二、線程池的基礎知識
2.1 線程池的核心組件
- 線程池管理器:負責線程池的創(chuàng)建、銷毀、管理和配置。
- 工作線程:工作線程是線程池中的實際工作者,負責執(zhí)行提交給線程池的任務。
- 任務接口:每個提交到線程池的任務都需要實現一個任務接口。
Runnable
用于定義無需返回值的任務,而Callable
則用于定義可以有返回值的任務,并且可以拋出異常。 - 工作隊列(任務隊列):用于暫存等待被執(zhí)行的任務,有多種實現方式,未必是先進先出。
- 線程工廠:線程工廠的作用是定義一個規(guī)范來創(chuàng)建線程,允許在創(chuàng)建線程時進行定制化設置,例如設置線程的名稱、優(yōu)先級、是否為守護線程(daemon thread)等屬性。
- 拒絕策略:見后。
(后三個核心組件是線程池的后三個配置參數)
2.2 JUC中的線程池架構
Executor
是線程池管理器的頂層接口,只定義了一個方法void execute(Runnable command)
,用來提交Runnable
任務。
ExecutorService
該接口擴展了Executor
,增加了更多管理線程池的方法,如提交Callable
任務、提交批量任務、線程池的生命周期管理(shutdown()
和shutdownNow()
)、獲取線程池狀態(tài)等。
AbstractExecutorService
實現了該接口中的部分方法,為創(chuàng)建自定義的線程池服務實現提供了基礎框架。
ThreadPoolExecutor
繼承自AbstractExecutorService
,是JUC中線程池管理器的核心實現類。
Executors
是一個靜態(tài)工廠類,提供了多種線程池管理器。
ScheduledExecutorService
該接口擴展了ExecutorService
,增加了定時執(zhí)行和周期性執(zhí)行任務的功能。ScheduledThreadPoolExecutor
實現該接口,專為計劃任務而設計。
2.3 線程池的配置參數
- corePoolSize:核心線程數。
- maximumPoolSize:最大線程數。
- keepAliveTime:空閑線程存活時間。當線程池中的工作線程數量超過核心線程數時,額外的工作線程在空閑這個時間段后會被終止,直到工作線程數量降到核心線程數。但如果設置了
allowCoreThreadTimeOut
為true
,那么核心線程也可以被終止。 - unit:空閑線程存活時間的單位,可以用枚舉類
TimeUnit
設置。 - workQueue:工作隊列。
- threadFactory:線程工廠。
- handler:拒絕策略。當阻塞隊列已滿且工作線程數大于等于最大線程數時,無法接受新的任務,會按照拒絕策略進行處理。
2.4 線程池常見的拒絕策略(可自定義)
- AbortPolicy:拒絕策略。是默認策略,會將新任務拒絕,并且拋出
RejectedExecutionException
異常。 - DiscardPolicy:拋棄策略。會將新任務丟掉,但不會跑出異常。
- DiscardOldestPolicy:拋棄最老任務策略。不是丟棄新任務,而是先丟棄最先進入隊列的任務,然后將新任務入隊。
- CallerRunsPolicy:調用者執(zhí)行策略。讓提交任務的線程去執(zhí)行新任務,而不是使用線程池中的線程去執(zhí)行。
2.5 線程池的生命周期
- New:新建狀態(tài)。當線程池創(chuàng)建后,但尚未開始執(zhí)行任務時,它處于新建狀態(tài)。此時,線程池中的線程尚未啟動。
- Running:運行狀態(tài)。新建的線程池開始接受并處理任務后就會進入運行狀態(tài),這是線程池的正常工作狀態(tài),可以接收新任務和處理工作隊列中的任務。
- Shutdown:關閉狀態(tài)。調用
shutdown()
方法后,線程池會進入關閉狀態(tài)。此時,線程池不再接受新任務,但不會立刻終止,它會繼續(xù)處理隊列中已有的任務直到所有任務完成。 - Stop:停止狀態(tài)。調用
shutdownNow()
方法后,線程池會進入停止狀態(tài),此時會中斷正在執(zhí)行任務的線程,清空任務隊列,并返回未開始執(zhí)行的任務列表。 - Tidying:整理狀態(tài)。完成關閉狀態(tài)或停止狀態(tài)的工作后,線程池會進入整理狀態(tài)。此階段,線程池中的線程數量降為0,即將調用
terminated()
鉤子方法。 - Terminated:終止狀態(tài)。在整理狀態(tài)后,線程池調用
terminated()
方法并進入終止狀態(tài)。此時線程池生命周期結束,會釋放所有資源。
2.6 線程池的任務調度規(guī)則
當向線程池提交一個任務時:
- 若線程池中的工作線程數量小于核心線程數,執(zhí)行器總是優(yōu)先創(chuàng)建一個新的工作線程來執(zhí)行任務,而不是使用一個空閑的工作線程(目的是快速讓線程池中有足夠的活躍線程)。若工作線程數量大于等于核心線程數,則根據工作隊列的情況進行相應的處理。
- 若工作隊列未滿,則將任務入隊。若工作隊列已滿,則根據工作線程數量與最大線程數的關系進行相應的處理。
- 若工作線程數大于等于最大線程數,則執(zhí)行拒絕策略。否則,會新建一個非核心線程來立即執(zhí)行新的任務。
2.7 線程池執(zhí)行器的鉤子方法
beforeExecute(Thread t, Runnable r)
: 任務執(zhí)行之前的鉤子方法。
afterExecute(Runnable r, Throwable t)
: 任務執(zhí)行之后的鉤子方法。
terminated()
: 線程池終止時的鉤子方法。
三、使用基礎
3.1 常見的工作隊列
工作隊列基于阻塞隊列實現,我們可以實現BlockingQueue
來自定義阻塞隊列,也可以使用JUC中提供的阻塞隊列:
- ArrayBlockingQueue:基于數組的有界阻塞隊列。隊列大小是固定的,在創(chuàng)建時必須指定。由于是基于數組,所以訪問速度快,但可能有擴容受限的問題。
- LinkedBlockingQueue:基于鏈表的阻塞隊列。如果不指定容量,則默認為Integer.MAX_VALUE。相比ArrayBlockingQueue,插入和刪除操作可能稍微慢一點,但由于鏈表的動態(tài)性,它可以更靈活地調整大小。
- PriorityBlockingQueue:基于最小堆的無界的優(yōu)先級隊列,元素按照自然排序或提供的比較器進行排序。任務按照優(yōu)先級順序被處理,而非先進先出。
- DelayQueue:基于
PriorityBlockingQueue
的無界阻塞隊列,其中元素只有在延遲期滿后才能被獲取,否則將阻塞等待,常用于實現定時任務和延遲操作。 - SynchronousQueue:一個特殊的隊列,它沒有內部容量,每個插入操作必須等待另一個線程的對應移除操作,反之亦然。適用于直接的生產者-消費者傳遞,非常適合傳遞性操作。
- LinkedTransferQueue:一個由鏈表結構組成的無界阻塞隊列,支持公平和非公平模式。它還提供了一個
tryTransfer()
方法,允許生產者直接將元素轉移給等待中的消費者,如果沒有消費者等待,則可以選擇是否阻塞(生產者可阻塞)。 - LinkedBlockingDeque:雙向鏈表實現的雙向阻塞隊列,既可以用作棧,也可以用作隊列。它支持有限或無界的隊列,并提供了
putFirst()
、putLast()
等方法來控制元素的插入位置。
3.2 Executors提供的線程池
3.3 使用Executors創(chuàng)建線程的問題
- 定長線程池/單線程話線程池:阻塞隊列無界,可能導致JVM出現OOM(Out Of Memory)異常。
- 定時線程池/可緩存線程池:最大線程數量不設限上,如果任務提交較多,就會造成大量的線程被啟動,可能造成OOM異常,也可能導致CPU線程資源耗盡。
四、開發(fā)實踐
4.1 線程數量配置
IO密集型
核心線程數設置得比CPU核心數稍大,因為當線程在等待I/O時,CPU可以調度其他線程執(zhí)行。最大線程數可以設置得更高,甚至遠大于CPU核心數,具體數值取決于系統(tǒng)的I/O能力及預期的并發(fā)水平。一般推薦設置為2 * CPU核心數或更高,但需注意不要設置得過高以免過度消耗系統(tǒng)資源。
計算密集型
核心線程數和最大線程數通常設置為CPU核心數,因為在這種情況下,更多的線程并不能帶來性能提升,反而會因為上下文切換帶來額外開銷。
4.2 提交任務:以submit(callable)為例
// 創(chuàng)建線程池ExecutorService executor = Executors.newSingleThreadExecutor();// 創(chuàng)建Callable任務對象Callable<Result> task = () -> {// ...};// 提交任務并獲取Future對象Future<Result> future = executor.submit(task); // 提交任務并獲取Future// 獲取結果try {// get()方法會阻塞,如果任務執(zhí)行過程中發(fā)生了異常,那么此處會拋出該異常Result res = future.get();} catch (InterruptedException e) {// 處理中斷異常...} catch (ExecutionException e) {// 處理執(zhí)行異常...}// 關閉ExecutorServiceexecutor.shutdown();
4.3 關閉線程池
調用shutdown()
或shutdownNow()
后,當前線程不會等待線程池的關閉,若當前線程結束了,可能導致內存泄露和資源浪費。
若線程池中還有活動的線程,就算主線程結束了,JVM也會因為存在活躍的非守護線程而無法退出,導致應用程序無法正常退出。
awaitTermination()
方法用來阻塞當前線程,直到線程池中的所有任務完成執(zhí)行或者超時,或者當前線程被中斷??梢杂糜诘却€程池中的線程全部執(zhí)行完后再退出當前線程,線程池中的所有線程在超時前執(zhí)行完畢,會返回true并恢復當前線程的執(zhí)行。
ExecutorService executorService = Executors.newFixedThreadPool(10);// ... 提交任務到線程池// 關閉線程池
executorService.shutdown();
try {// 等待一定時間,直到超時或者線程池中的線程全部執(zhí)行結束if (!executorService.awaitTermination(60, TimeUnit.SECONDS)) {// 如果超時,則嘗試強制關閉executorService.shutdownNow();// 等待一定時間,直到超時或者線程池中的線程全部執(zhí)行結束if (!executorService.awaitTermination(60, TimeUnit.SECONDS))System.err.println("線程池未在規(guī)定時間內停止!");}
} catch (InterruptedException ie) {// 強制終止線程池executorService.shutdownNow();// 保持當前線程的中斷狀態(tài)Thread.currentThread().interrupt();
}
參考
Java線程池(超詳細)
Java 多線程:徹底搞懂線程池