new Thread弊端
- 每次啟動線程都需要new Thread新建對象與線程连茧,性能差。線程池能重用存在的線程谒拴,減少對象創(chuàng)建尝江、回收的開銷。
- 線程缺乏統(tǒng)一管理英上,可以無限制的新建線程炭序,導(dǎo)致OOM。線程池可以控制可以創(chuàng)建苍日、執(zhí)行的最大并發(fā)線程數(shù)惭聂。
- 缺少工程實(shí)踐的一些高級的功能如定期執(zhí)行、線程中斷相恃。線程池提供定期執(zhí)行彼妻、并發(fā)數(shù)控制功能
ThreadPoolExecutor
核心變量
在創(chuàng)建線程池時(shí)需要傳入的參數(shù)
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
- corePoolSize:核心線程數(shù)量,線程池中應(yīng)該常駐的線程數(shù)量
- maximumPoolSize:線程池允許的最大線程數(shù)豆茫,非核心線程在超時(shí)之后會被清除
- workQueue:阻塞隊(duì)列,存儲等待執(zhí)行的任務(wù)
- keepAliveTime:線程沒有任務(wù)執(zhí)行時(shí)可以保持的時(shí)間
- unit:時(shí)間單位
- threadFactory:線程工廠屋摇,來創(chuàng)建線程
- rejectHandler:當(dāng)拒絕任務(wù)提交時(shí)的策略(拋異常揩魂、用調(diào)用者所在的線程執(zhí)行任務(wù)、丟棄隊(duì)列中第一個(gè)任務(wù)執(zhí)行當(dāng)前任務(wù)炮温、直接丟棄任務(wù))
創(chuàng)建線程的邏輯
以下任務(wù)提交邏輯來自ThreadPoolExecutor.execute方法:
- 如果運(yùn)行的線程數(shù) < corePoolSize火脉,直接創(chuàng)建新線程,即使有其他線程是空閑的
- 如果運(yùn)行的線程數(shù) >= corePoolSize
2.1 如果插入隊(duì)列成功柒啤,則完成本次任務(wù)提交倦挂,但不創(chuàng)建新線程
2.2 如果插入隊(duì)列失敗,說明隊(duì)列滿了
2.2.1 如果當(dāng)前線程數(shù) < maximumPoolSize担巩,創(chuàng)建新的線程放到線程池中
2.2.2 如果當(dāng)前線程數(shù) >= maximumPoolSize方援,會執(zhí)行指定的拒絕策略
阻塞隊(duì)列的策略
- 直接提交。SynchronousQueue涛癌,它將任務(wù)直接提交給線程而不保持它們犯戏。如果不存在可用于立即運(yùn)行任務(wù)的線程送火,則試圖把任務(wù)加入隊(duì)列將失敗,因此會構(gòu)造一個(gè)新的線程先匪。此策略可以避免在處理可能具有內(nèi)部依賴性的請求集時(shí)出現(xiàn)鎖种吸。直接提交通常要求無界maximumPoolSizes 以避免拒絕新提交的任務(wù)。
- 無界隊(duì)列呀非。使用無界隊(duì)列(例如坚俗,不具有預(yù)定義容量的 LinkedBlockingQueue)將導(dǎo)致在所有 corePoolSize線程都忙時(shí)新任務(wù)在隊(duì)列中等待。這樣岸裙,創(chuàng)建的線程就不會超過 corePoolSize猖败。(因此,maximumPoolSize的值也就無效了哥桥。)當(dāng)每個(gè)任務(wù)完全獨(dú)立于其他任務(wù)辙浑,即任務(wù)執(zhí)行互不影響時(shí),適合于使用無界隊(duì)列拟糕;例如判呕,在 Web頁服務(wù)器中。這種排隊(duì)可用于處理瞬態(tài)突發(fā)請求送滞,當(dāng)命令以超過隊(duì)列所能處理的平均數(shù)連續(xù)到達(dá)時(shí)侠草,此策略允許無界線程具有增長的可能性。
- 有界隊(duì)列犁嗅。當(dāng)使用有限的 maximumPoolSizes 時(shí)边涕,有界隊(duì)列(如ArrayBlockingQueue)有助于防止資源耗盡,但是可能較難調(diào)整和控制褂微。隊(duì)列大小和最大池大小可能需要相互折衷:使用大型隊(duì)列和小型池可以最大限度地降低CPU 使用率功蜓、操作系統(tǒng)資源和上下文切換開銷,但是可能導(dǎo)致人工降低吞吐量宠蚂。如果任務(wù)頻繁阻塞(例如式撼,如果它們是 I/O邊界),則系統(tǒng)可能為超過您許可的更多線程安排時(shí)間求厕。使用小型隊(duì)列通常要求較大的池大小著隆,CPU使用率較高,但是可能遇到不可接受的調(diào)度開銷呀癣,這樣也會降低吞吐量美浦。
執(zhí)行線程的邏輯
如果線程能被創(chuàng)建,那么在ThreadPoolExecutor的addWorker方法中项栏,會將我們傳進(jìn)去的Runnable轉(zhuǎn)換成內(nèi)部的繼承自AQS的Worker類(new Worker(firstTask);
)浦辨,在其中的run方法中不斷從任務(wù)隊(duì)列中獲取任務(wù)去執(zhí)行
關(guān)鍵方法
- execute:提交任務(wù)
- submit:提交任務(wù),能夠得到執(zhí)行結(jié)果
- shutdown:等待任務(wù)執(zhí)行完再關(guān)閉線程池
- shutdownNow:不等待直接關(guān)閉線程池
常用工具
Executors是一個(gè)工具類沼沈,能快速創(chuàng)建實(shí)用的線程池荤牍,但是返回的ExecuteService接口缺少很多ThreadPoolExecutor的方法需要注意
Executors.newCachedThreadPool()
corePoolSize為0案腺,maximumPoolSize為整數(shù)最大值,keepAliveTime為60秒康吵,隊(duì)列為SynchronousQueue
創(chuàng)建一個(gè)可緩存線程池劈榨,如果有空閑線程則交給新任務(wù),否則創(chuàng)建新的線程晦嵌。
Executors.newFixedThreadPool()
corePoolSize同辣,maximumPoolSize自定義,keepAliveTime為0秒惭载,隊(duì)列為LinkedBlockingQueue
創(chuàng)建一個(gè)定長線程池旱函,可控制線程最大并發(fā)數(shù),超出的線程會在隊(duì)列中等待描滔。
Executors.newScheduledThreadPool()
corePoolSize自定義棒妨,maximumPoolSize為整數(shù)最大值,keepAliveTime為0秒含长,隊(duì)列為DelayedWorkQueue
創(chuàng)建一個(gè)定長線程池券腔,支持定時(shí)及周期性任務(wù)執(zhí)行。
Executors.newSingleThreadExecutor()
corePoolSize拘泞,maximumPoolSize為1纷纫,keepAliveTime為0秒,隊(duì)列為LinkedBlockingQueue
創(chuàng)建一個(gè)單線程化的線程池陪腌,它只會用唯一的工作線程來執(zhí)行任務(wù)辱魁,保證所有任務(wù)按照指定順序(FIFO, LIFO, 優(yōu)先級)執(zhí)行
例子
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
public class ThreadPoolTest {
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
int finalI = i;
executorService.execute(() -> System.out.println(finalI));
}
executorService.shutdown();
}
}
以上代碼將非順序輸出09,類似于fixed诗鸭,但single的將順序輸出09
<figcaption style="display: block; text-align: center; font-size: 1rem; line-height: 1.6; color: rgb(144, 144, 144); margin-top: 2px;"></figcaption>
public class ThreadPoolTest {
public static void main(String[] args) {
ScheduledExecutorService executorService = Executors.newScheduledThreadPool(3);
// executorService.schedule(() -> System.out.println("hehe"), 1, TimeUnit.SECONDS);
executorService.scheduleAtFixedRate(() -> System.out.println("hehe"), 1, 2, TimeUnit.SECONDS);
// executorService.shutdown();
}
}
以上代碼是newScheduledThreadPool的典型使用方式染簇,將按照計(jì)劃的方式來執(zhí)行任務(wù)
配置線程池的建議
- CPU密集型任務(wù):CPU數(shù) + 1
- IO密集型任務(wù):CPU數(shù) * 2
先將線程池大小設(shè)置為參考值,再觀察任務(wù)運(yùn)行情況和系統(tǒng)負(fù)載强岸、資源利用率來進(jìn)行適當(dāng)調(diào)整剖笙。
PS:給大家推薦一個(gè)java資源共享,學(xué)習(xí)交流群《957734884》