面試?yán)蠍?ài)問(wèn)的線程池,一把梭哈!
低并發(fā)編程,周一很頹廢,周四很硬核

閃客:沒(méi)問(wèn)題,這個(gè)我擅長(zhǎng),咱們從一個(gè)最簡(jiǎn)單的情況開(kāi)始,假設(shè)有一段代碼,你希望異步執(zhí)行它,是不是要寫(xiě)出這樣的代碼?
new?Thread(r).start();
閃客:這種寫(xiě)法當(dāng)然可以完成功能,可是你這樣寫(xiě),老王這樣寫(xiě),老張也這樣寫(xiě),程序中到處都是這樣創(chuàng)建線程的方法,能不能寫(xiě)一個(gè)統(tǒng)一的工具類(lèi)讓大家調(diào)用呢?
小宇:可以的,感覺(jué)有一個(gè)統(tǒng)一的工具類(lèi),更優(yōu)雅一些。
閃客:那如果讓你來(lái)設(shè)計(jì)這個(gè)工具類(lèi),你會(huì)怎么寫(xiě)呢?我先給你定一個(gè)接口,你來(lái)實(shí)現(xiàn)。
public?interface?Executor?{
????public?void?execute(Runnable?r);
}
閃客:STOP!小宇呀,你現(xiàn)在深受面試手冊(cè)的毒害,你先把這些全部的概念忘掉,就說(shuō)讓你寫(xiě)一個(gè)最簡(jiǎn)單的工具類(lèi),第一反應(yīng),你會(huì)怎么寫(xiě)?
第一版
小宇:那我可能會(huì)這樣
//?新線程:直接創(chuàng)建一個(gè)新線程運(yùn)行
class?FlashExecutor?implements?Executor?{
????public?void?execute(Runnable?r)?{
????????new?Thread(r).start();
????}
}
小宇:啊,我這個(gè)會(huì)不會(huì)太 low 了呀,我還以為你會(huì)罵我呢。
怎么會(huì), Doug Lea 大神在 JDK 源碼注釋中給出的就是這樣的例子,這是最根本的功能。你在這個(gè)基礎(chǔ)上,嘗試著優(yōu)化一下?
第二版
小宇:還能怎么優(yōu)化呢?這不已經(jīng)用一個(gè)工具類(lèi)實(shí)現(xiàn)了異步執(zhí)行了嘛!
閃客:我問(wèn)你一個(gè)問(wèn)題,假如有 10000 個(gè)人都調(diào)用這個(gè)工具類(lèi)提交任務(wù),那就會(huì)創(chuàng)建 10000 個(gè)線程來(lái)執(zhí)行,這肯定不合適吧!能不能控制一下線程的數(shù)量呢?
小宇:這不難,我可以把這個(gè)任務(wù) r 丟到一個(gè) tasks 隊(duì)列中,然后只啟動(dòng)一個(gè)線程,就叫它 Worker 線程吧,不斷從 tasks 隊(duì)列中取任務(wù),執(zhí)行任務(wù)。這樣無(wú)論調(diào)用者調(diào)用多少次,永遠(yuǎn)就只有一個(gè) Worker 線程在運(yùn)行,像這樣。

閃客:太棒了,這個(gè)設(shè)計(jì)有了三個(gè)重大的意義:
1. 控制了線程數(shù)量。
2. 隊(duì)列不但起到了緩沖的作用,還將任務(wù)的提交與執(zhí)行解耦了。
3. 最重要的一點(diǎn)是,解決了每次重復(fù)創(chuàng)建和銷(xiāo)毀線程帶來(lái)的系統(tǒng)開(kāi)銷(xiāo)。
小宇:哇真的么,這么小的改動(dòng)有這么多意義呀。
閃客:那當(dāng)然,不過(guò)只有一個(gè)后臺(tái)的工作線程 Worker 會(huì)不會(huì)少了點(diǎn)?還有如果這個(gè) tasks 隊(duì)列滿(mǎn)了怎么辦呢?
第三版
小宇:哦,的確,只有一個(gè)線程在某些場(chǎng)景下是很吃力的,那我把 Worker 線程的數(shù)量增加?
閃客:沒(méi)錯(cuò),Worker 線程的數(shù)量要增加,但是具體數(shù)量要讓使用者決定,調(diào)用時(shí)傳入,就叫核心線程數(shù) corePoolSize?吧。
小宇:好的,那我這樣設(shè)計(jì)。
1. 初始化線程池時(shí),直接啟動(dòng) corePoolSize 個(gè)工作線程 Worker 先跑著。
2. 這些 Worker 就是死循環(huán)從隊(duì)列里取任務(wù)然后執(zhí)行。
3. execute 方法仍然是直接把任務(wù)放到隊(duì)列,但隊(duì)列滿(mǎn)了之后直接拋棄

閃客:太完美了,獎(jiǎng)勵(lì)你一塊費(fèi)列羅吧。
小宇:哈哈謝謝,那我先吃一會(huì)兒哈。?
閃客:好,你邊吃我邊說(shuō)。現(xiàn)在我們已經(jīng)實(shí)現(xiàn)了一個(gè)至少不那么丑陋的線程池了,但還有幾處小瑕疵,比如初始化的時(shí)候,就創(chuàng)建了一堆 Worker 線程在那空跑著,假如此時(shí)并沒(méi)有異步任務(wù)提交過(guò)來(lái)執(zhí)行,這就有點(diǎn)浪費(fèi)了。
小宇:哦好像是誒!
閃客:還有,你這隊(duì)列一滿(mǎn),就直接把新任務(wù)丟棄了,這樣有些粗暴,能不能讓調(diào)用者自己決定該怎么處理呢?
小宇:哎呀,想不到我這么溫柔的妹紙居然寫(xiě)出了這么粗暴的代碼。
閃客:額,你先把費(fèi)列羅咽下去吧。
第四版
小宇:我吃完了,現(xiàn)在腦子有點(diǎn)不夠用了,得先消化消化食物,要不你幫我分析分析吧。
閃客:好的,現(xiàn)在我們做出如下改進(jìn)。
1. 按需創(chuàng)建Worker:剛初始化線程池時(shí),不再立刻創(chuàng)建 corePoolSize 個(gè)工作線程,而是等待調(diào)用者不斷提交任務(wù)的過(guò)程中,逐漸把工作線程 Worker 創(chuàng)建出來(lái),等數(shù)量達(dá)到 corePoolSize 時(shí)就停止,把任務(wù)直接丟到隊(duì)列里。那就必然要用一個(gè)屬性記錄已經(jīng)創(chuàng)建出來(lái)的工作線程數(shù)量,就叫 workCount 吧。
2. 加拒絕策略:實(shí)現(xiàn)上就是增加一個(gè)入?yún)ⅲ?lèi)型是一個(gè)接口 RejectedExecutionHandler,由調(diào)用者決定實(shí)現(xiàn)類(lèi),以便在任務(wù)提交失敗后執(zhí)行 rejectedExecution 方法。
3. 增加線程工廠:實(shí)現(xiàn)上就是增加一個(gè)入?yún)ⅲ?lèi)型是一個(gè)接口 ThreadFactory,增加工作線程時(shí)不再直接 new 線程,而是調(diào)用這個(gè)由調(diào)用者傳入的 ThreadFactory 實(shí)現(xiàn)類(lèi)的 newThread 方法。
就像下面這樣。

彈性思維

第五版
第五版




1. 開(kāi)始的時(shí)候和上一版一樣,當(dāng) workCount < corePoolSize 時(shí),通過(guò)創(chuàng)建新的 Worker 來(lái)執(zhí)行任務(wù)。
2. 當(dāng) workCount >= corePoolSize 就停止創(chuàng)建新線程,把任務(wù)直接丟到隊(duì)列里。
3. 但當(dāng)隊(duì)列已滿(mǎn)且仍然 workCount < maximumPoolSize 時(shí),不再直接走拒絕策略,而是創(chuàng)建非核心線程,直到 workCount = maximumPoolSize,再走拒絕策略。


總結(jié)
總結(jié)
public?FlashExecutor(
????????int?corePoolSize,
????????int?maximumPoolSize,
????????long?keepAliveTime,
????????TimeUnit?unit,
????????BlockingQueue?workQueue, ?
????????ThreadFactory?threadFactory,
????????RejectedExecutionHandler?handler)
{
????...?//?省略一些參數(shù)校驗(yàn)
????this.corePoolSize?=?corePoolSize;
????this.maximumPoolSize?=?maximumPoolSize;
????this.workQueue?=?workQueue;
????this.keepAliveTime?=?unit.toNanos(keepAliveTime);
????this.threadFactory?=?threadFactory;
????this.handler?=?handler;
}
這些參數(shù)分別是

后記?
