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