線程池:是生產(chǎn)者-消費(fèi)者模型的典型應(yīng)用据某。應(yīng)用程序?qū)⑷蝿?wù)交給線程池,線程池將其放入到BlockingQueue中寄纵。線程池中的已經(jīng)開啟的worker線程會循環(huán)監(jiān)聽BlockingQueue中的任務(wù)來進(jìn)行消費(fèi)鳖敷。
1. 簡述:線程池的設(shè)計(jì)理念
使用Thread開啟線程:
- 執(zhí)行start()方法。線程才會真正異步的執(zhí)行程拭。
- 執(zhí)行run()方法定踱,線程同步的執(zhí)行。
當(dāng)然恃鞋,一般我們會執(zhí)行start方法崖媚。
若使用Thread開啟線程:每一次子線程均需要經(jīng)歷創(chuàng)建和銷毀的生命周期亦歉,性能不好。
為了解決這個(gè)問題至扰,JDK設(shè)計(jì)出線程池鳍徽。
1.1 生產(chǎn)消費(fèi)者模型
JDK的BlockingQueue,天生實(shí)現(xiàn)的是生產(chǎn)者-消費(fèi)者模型敢课,即隊(duì)列滿了put隊(duì)列會被阻塞阶祭;隊(duì)列空了后get方法會被阻塞。
work線程將開啟while循環(huán)的監(jiān)聽
線程池中Worker線程開啟后直秆。使用while循環(huán)消費(fèi)BlockingQueue中的Runnable濒募。若while循環(huán)時(shí)得到的任務(wù)為空,那么便去關(guān)閉Worker線程圾结。這也就是最大線程數(shù)和超過空閑時(shí)間的核心線程數(shù)能自動回收的原因瑰剃。
核心線程最大空閑時(shí)間:
核心線程數(shù)的空閑時(shí)間KeepAliveTime
。其實(shí)就是阻塞隊(duì)列獲取Running的最大等待時(shí)間筝野。當(dāng)配置了該參數(shù)且超過最大等待時(shí)間晌姚,那么返回一個(gè)null任務(wù)。而worker線程的run方法中的while循環(huán)得到null時(shí)歇竟,便會結(jié)束這個(gè)worker線程挥唠。
poll獲取到的元素為空,那么便會結(jié)束監(jiān)聽焕议,worker線程的run方法執(zhí)行完畢宝磨。
- get:向阻塞隊(duì)列獲取元素,當(dāng)阻塞隊(duì)列空了之后盅安,put時(shí)會被阻塞唤锉。
- poll(1,TimeUnit.MILLISECONDS):如果隊(duì)列為空,阻塞一段時(shí)間后依舊為null别瞭,則返回null窿祥。
當(dāng)返回null時(shí),由“work線程將開啟while循環(huán)的監(jiān)聽”可值蝙寨,將結(jié)束worker線程的監(jiān)聽操作壁肋。
生產(chǎn)者和拒絕策略:
生產(chǎn)者向BlockingQueue中放入消息時(shí),并沒有使用put方法籽慢,而是使用的offer方法浸遗。
- put:向阻塞隊(duì)列填充元素,當(dāng)阻塞隊(duì)列滿了之后箱亿,put時(shí)會被阻塞跛锌。
- offer:向阻塞隊(duì)列填充元素,當(dāng)阻塞隊(duì)列滿了之后,offer會返回false髓帽。
拒絕策略:因?yàn)閛ffer方法返回false菠赚,說明阻塞隊(duì)列已經(jīng)滿了,若最大線程數(shù)大于核心線程數(shù)郑藏,那么繼續(xù)開啟Worker線程衡查。若開啟的是最大線程數(shù)的Worker線程,那么就會執(zhí)行配置的拒絕策略必盖。
本質(zhì)上就是一個(gè)內(nèi)存級別的RabbitMQ
1.2 線程池的原理簡述
線程池里面的線程為消費(fèi)者線程(worker線程)【消費(fèi)者循環(huán)去處理任務(wù)】拌牲,默認(rèn)情況下,必須有任務(wù)的到來才會觸發(fā)創(chuàng)建消費(fèi)者歌粥。也可以使用
prestartAllCoreThreads
方法塌忽,在線程池初始化時(shí),將所有的消費(fèi)者全部創(chuàng)建出來失驶。消費(fèi)者監(jiān)聽的是
BlockingQueue
隊(duì)列(默認(rèn)實(shí)現(xiàn)生產(chǎn)消費(fèi)者模式)土居,消費(fèi)者調(diào)用workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS)
方法拉取任務(wù),若是在指定時(shí)間內(nèi)獲取不到任務(wù)嬉探,便會將自己注銷掉(也就是結(jié)束監(jiān)聽)擦耀。生產(chǎn)者向BlockingQueue存入任務(wù)時(shí),使用的是offer方法涩堤,即隊(duì)列滿了之后返回false眷蜓,然后判斷最大線程數(shù)是否在開啟Worker線程以及任務(wù)的拒絕策略。
1.3 線程池任務(wù)執(zhí)行流程
- 當(dāng)線程池中線程數(shù)量小于corePoolSize定躏,每來一個(gè)任務(wù)账磺,就會創(chuàng)建一個(gè)線程執(zhí)行這個(gè)任務(wù)芹敌。
- 當(dāng)前線程池線程數(shù)量大于等于corePoolSize痊远,則每來一個(gè)任務(wù)。會嘗試將其添加到任務(wù)緩存隊(duì)列中氏捞,若是添加成功碧聪,則該任務(wù)會等待線程將其取出去執(zhí)行;若添加失斠壕ァ(一般來說任務(wù)緩存隊(duì)列已滿)逞姿,則會嘗試創(chuàng)建新的線程執(zhí)行。
- 當(dāng)前線程池線程數(shù)量等于maximumPoolSize捆等,則會采取任務(wù)拒絕策略進(jìn)行處理滞造。
- 當(dāng)前線程池線程數(shù)量大于corePoolSize,如果某線程空閑時(shí)間超過keepAliveTime栋烤,線程將會被終止谒养,直到不大于corePoolSize數(shù)量。如果允許為核心池(corePoolSize)中的線程設(shè)置存活時(shí)間明郭,那么核心池中的線程空閑時(shí)間超過keepAliveTime买窟,線程也會被終止丰泊。
1.4 拒絕策略:一個(gè)也不放棄
有時(shí)候,我們不想丟棄任務(wù)始绍,且將生產(chǎn)者(主線程)阻塞瞳购,那么如何實(shí)現(xiàn)拒絕策略?
上文我們得知亏推,生產(chǎn)者放入queue時(shí)使用的是offer方法学赛,當(dāng)阻塞隊(duì)列滿了之后,直接返回false径簿。我們可以采用put的方式罢屈,將主線程阻塞。不在向BlockingQueue中填充任務(wù)篇亭。
ThreadPoolExecutor executor2 = new ThreadPoolExecutor(1, 1, 100
, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>(50),((r, executor) -> {
try {
//阻塞主線程
executor.getQueue().put(r);
} catch (InterruptedException e) {
}
}));
詳細(xì)源碼分析
JAVA并發(fā)(12)— Lock實(shí)現(xiàn)生產(chǎn)者消費(fèi)者
JAVA并發(fā)(13)— ThreadPoolExecutor的實(shí)現(xiàn)原理(源碼分析)