1. 為什么要使用線程池
在實(shí)際使用中,線程是很占用系統(tǒng)資源的,如果對(duì)線程管理不善很容易導(dǎo)致系統(tǒng)問題。因此,在大多數(shù)并發(fā)框架中都會(huì)使用線程池來管理線程,使用線程池管理線程主要有如下好處:
降低資源消耗。通過復(fù)用已存在的線程和降低線程關(guān)閉的次數(shù)來盡可能降低系統(tǒng)性能損耗;
提升系統(tǒng)響應(yīng)速度。通過復(fù)用線程,省去創(chuàng)建線程的過程,因此整體上提升了系統(tǒng)的響應(yīng)速度;
提高線程的可管理性。線程是稀缺資源,如果無限制的創(chuàng)建,不僅會(huì)消耗系統(tǒng)資源,還會(huì)降低系統(tǒng)的穩(wěn)定性,因此,需要使用線程池來管理線程。
2. 線程池的工作原理
當(dāng)一個(gè)并發(fā)任務(wù)提交給線程池,線程池分配線程去執(zhí)行任務(wù)的過程如下圖所示:

線程池執(zhí)行流程圖.jpg
從圖可以看出,線程池執(zhí)行所提交的任務(wù)過程主要有這樣幾個(gè)階段:
先判斷線程池中核心線程池所有的線程是否都在執(zhí)行任務(wù)。如果不是,則新創(chuàng)建一個(gè)線程執(zhí)行剛提交的任務(wù),否則,核心線程池中所有的線程都在執(zhí)行任務(wù),則進(jìn)入第 2 步;
判斷當(dāng)前阻塞隊(duì)列是否已滿,如果未滿,則將提交的任務(wù)放置在阻塞隊(duì)列中;否則,則進(jìn)入第 3 步;
判斷線程池中所有的線程是否都在執(zhí)行任務(wù),如果沒有,則創(chuàng)建一個(gè)新的線程來執(zhí)行任務(wù),否則,則交給飽和策略進(jìn)行處理
3.線程池的實(shí)現(xiàn)
不懂的朋友可以看看這個(gè),線程池現(xiàn)實(shí)的視頻講解,點(diǎn)擊:150行代碼,手寫線程池
4. 線程池的創(chuàng)建
創(chuàng)建線程池主要是ThreadPoolExecutor類來完成,ThreadPoolExecutor 的有許多重載的構(gòu)造方法,通過參數(shù)最多的構(gòu)造方法來理解創(chuàng)建線程池有哪些需要配置的參數(shù)。ThreadPoolExecutor 的構(gòu)造方法為:
ThreadPoolExecutor(int corePoolSize,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? int maximumPoolSize,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? long keepAliveTime,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? TimeUnit unit,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? BlockingQueue<Runnable> workQueue,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ThreadFactory threadFactory,
? ? ? ? ? ? ? ? ? ? ? ? ? ? ? RejectedExecutionHandler handler)
下面對(duì)參數(shù)進(jìn)行說明:
corePoolSize:表示核心線程池的大小。當(dāng)提交一個(gè)任務(wù)時(shí),如果當(dāng)前核心線程池的線程個(gè)數(shù)沒有達(dá)到 corePoolSize,則會(huì)創(chuàng)建新的線程來執(zhí)行所提交的任務(wù),即使當(dāng)前核心線程池有空閑的線程。如果當(dāng)前核心線程池的線程個(gè)數(shù)已經(jīng)達(dá)到了 corePoolSize,則不再重新創(chuàng)建線程。如果調(diào)用了prestartCoreThread()或者 prestartAllCoreThreads(),線程池創(chuàng)建的時(shí)候所有的核心線程都會(huì)被創(chuàng)建并且啟動(dòng)。
maximumPoolSize:表示線程池能創(chuàng)建線程的最大個(gè)數(shù)。如果當(dāng)阻塞隊(duì)列已滿時(shí),并且當(dāng)前線程池線程個(gè)數(shù)沒有超過 maximumPoolSize 的話,就會(huì)創(chuàng)建新的線程來執(zhí)行任務(wù)。
keepAliveTime:空閑線程存活時(shí)間。如果當(dāng)前線程池的線程個(gè)數(shù)已經(jīng)超過了 corePoolSize,并且線程空閑時(shí)間超過了 keepAliveTime 的話,就會(huì)將這些空閑線程銷毀,這樣可以盡可能降低系統(tǒng)資源消耗。
unit:時(shí)間單位。為 keepAliveTime 指定時(shí)間單位。
workQueue:阻塞隊(duì)列。用于保存任務(wù)的阻塞隊(duì)列,關(guān)于阻塞隊(duì)列可以看這篇文章。可以使用ArrayBlockingQueue, LinkedBlockingQueue, SynchronousQueue, PriorityBlockingQueue。
threadFactory:創(chuàng)建線程的工程類。可以通過指定線程工廠為每個(gè)創(chuàng)建出來的線程設(shè)置更有意義的名字,如果出現(xiàn)并發(fā)問題,也方便查找問題原因。
handler:飽和策略。當(dāng)線程池的阻塞隊(duì)列已滿和指定的線程都已經(jīng)開啟,說明當(dāng)前線程池已經(jīng)處于飽和狀態(tài)了,那么就需要采用一種策略來處理這種情況。采用的策略有這幾種:
AbortPolicy: 直接拒絕所提交的任務(wù),并拋出RejectedExecutionException異常;
CallerRunsPolicy:只用調(diào)用者所在的線程來執(zhí)行任務(wù);
DiscardPolicy:不處理直接丟棄掉任務(wù);
DiscardOldestPolicy:丟棄掉阻塞隊(duì)列中存放時(shí)間最久的任務(wù),執(zhí)行當(dāng)前任務(wù)
分享更多關(guān)于C/C++ Linux后端開發(fā)網(wǎng)絡(luò)底層原理知識(shí)學(xué)習(xí)提升? 學(xué)習(xí)資料,完善技術(shù)棧,內(nèi)容知識(shí)點(diǎn)包括Linux,Nginx,ZeroMQ,MySQL,Redis,線程池,MongoDB,ZK,流媒體,音視頻,Linux內(nèi)核,CDN,P2P,epoll,Docker,TCP/IP,協(xié)程,DPDK等等。

線程池執(zhí)行邏輯
通過 ThreadPoolExecutor 創(chuàng)建線程池后,提交任務(wù)后執(zhí)行過程是怎樣的,下面來通過源碼來看一看。execute 方法源碼如下:
public void execute(Runnable command) {
? ? if (command == null)
? ? ? ? throw new NullPointerException();
? ? /*
? ? * Proceed in 3 steps:
? ? *
? ? * 1. If fewer than corePoolSize threads are running, try to
? ? * start a new thread with the given command as its first
? ? * task.? The call to addWorker atomically checks runState and
? ? * workerCount, and so prevents false alarms that would add
? ? * threads when it shouldn't, by returning false.
? ? *
? ? * 2. If a task can be successfully queued, then we still need
? ? * to double-check whether we should have added a thread
? ? * (because existing ones died since last checking) or that
? ? * the pool shut down since entry into this method. So we
? ? * recheck state and if necessary roll back the enqueuing if
? ? * stopped, or start a new thread if there are none.
? ? *
? ? * 3. If we cannot queue task, then we try to add a new
? ? * thread.? If it fails, we know we are shut down or saturated
? ? * and so reject the task.
? ? */
? ? int c = ctl.get();
//如果線程池的線程個(gè)數(shù)少于corePoolSize則創(chuàng)建新線程執(zhí)行當(dāng)前任務(wù)
? ? if (workerCountOf(c) < corePoolSize) {
? ? ? ? if (addWorker(command, true))
? ? ? ? ? ? return;
? ? ? ? c = ctl.get();
? ? }
//如果線程個(gè)數(shù)大于corePoolSize或者創(chuàng)建線程失敗,則將任務(wù)存放在阻塞隊(duì)列workQueue中
? ? if (isRunning(c) && workQueue.offer(command)) {
? ? ? ? int recheck = ctl.get();
? ? ? ? if (! isRunning(recheck) && remove(command))
? ? ? ? ? ? reject(command);
? ? ? ? else if (workerCountOf(recheck) == 0)
? ? ? ? ? ? addWorker(null, false);
? ? }
//如果當(dāng)前任務(wù)無法放進(jìn)阻塞隊(duì)列中,則創(chuàng)建新的線程來執(zhí)行任務(wù)
? ? else if (!addWorker(command, false))
? ? ? ? reject(command);
}
ThreadPoolExecutor 的 execute 方法執(zhí)行邏輯請(qǐng)見注釋。下圖為 ThreadPoolExecutor 的 execute 方法的執(zhí)行示意圖:

execute執(zhí)行過程示意圖.jpg
execute 方法執(zhí)行邏輯有這樣幾種情況:
如果當(dāng)前運(yùn)行的線程少于 corePoolSize,則會(huì)創(chuàng)建新的線程來執(zhí)行新的任務(wù);
如果運(yùn)行的線程個(gè)數(shù)等于或者大于 corePoolSize,則會(huì)將提交的任務(wù)存放到阻塞隊(duì)列 workQueue 中;
如果當(dāng)前 workQueue 隊(duì)列已滿的話,則會(huì)創(chuàng)建新的線程來執(zhí)行任務(wù);
如果線程個(gè)數(shù)已經(jīng)超過了 maximumPoolSize,則會(huì)使用飽和策略 RejectedExecutionHandler 來進(jìn)行處理。
需要注意的是,線程池的設(shè)計(jì)思想就是使用了核心線程池 corePoolSize,阻塞隊(duì)列 workQueue 和線程池 maximumPoolSize,這樣的緩存策略來處理任務(wù),實(shí)際上這樣的設(shè)計(jì)思想在需要框架中都會(huì)使用。
5. 線程池的關(guān)閉
關(guān)閉線程池,可以通過shutdown和shutdownNow這兩個(gè)方法。它們的原理都是遍歷線程池中所有的線程,然后依次中斷線程。shutdown和shutdownNow還是有不一樣的地方:
shutdownNow首先將線程池的狀態(tài)設(shè)置為STOP,然后嘗試停止所有的正在執(zhí)行和未執(zhí)行任務(wù)的線程,并返回等待執(zhí)行任務(wù)的列表;
shutdown只是將線程池的狀態(tài)設(shè)置為SHUTDOWN狀態(tài),然后中斷所有沒有正在執(zhí)行任務(wù)的線程
可以看出 shutdown 方法會(huì)將正在執(zhí)行的任務(wù)繼續(xù)執(zhí)行完,而 shutdownNow 會(huì)直接中斷正在執(zhí)行的任務(wù)。調(diào)用了這兩個(gè)方法的任意一個(gè),isShutdown方法都會(huì)返回 true,當(dāng)所有的線程都關(guān)閉成功,才表示線程池成功關(guān)閉,這時(shí)調(diào)用isTerminated方法才會(huì)返回 true。
5. 如何合理配置線程池參數(shù)?
要想合理的配置線程池,就必須首先分析任務(wù)特性,可以從以下幾個(gè)角度來進(jìn)行分析:
任務(wù)的性質(zhì):CPU 密集型任務(wù),IO 密集型任務(wù)和混合型任務(wù)。
任務(wù)的優(yōu)先級(jí):高,中和低。
任務(wù)的執(zhí)行時(shí)間:長(zhǎng),中和短。
任務(wù)的依賴性:是否依賴其他系統(tǒng)資源,如數(shù)據(jù)庫連接。
任務(wù)性質(zhì)不同的任務(wù)可以用不同規(guī)模的線程池分開處理。CPU 密集型任務(wù)配置盡可能少的線程數(shù)量,如配置Ncpu+1個(gè)線程的線程池。IO 密集型任務(wù)則由于需要等待 IO 操作,線程并不是一直在執(zhí)行任務(wù),則配置盡可能多的線程,如2xNcpu?;旌闲偷娜蝿?wù),如果可以拆分,則將其拆分成一個(gè) CPU 密集型任務(wù)和一個(gè) IO 密集型任務(wù),只要這兩個(gè)任務(wù)執(zhí)行的時(shí)間相差不是太大,那么分解后執(zhí)行的吞吐率要高于串行執(zhí)行的吞吐率,如果這兩個(gè)任務(wù)執(zhí)行時(shí)間相差太大,則沒必要進(jìn)行分解。我們可以通過Runtime.getRuntime().availableProcessors()方法獲得當(dāng)前設(shè)備的 CPU 個(gè)數(shù)。
優(yōu)先級(jí)不同的任務(wù)可以使用優(yōu)先級(jí)隊(duì)列 PriorityBlockingQueue 來處理。它可以讓優(yōu)先級(jí)高的任務(wù)先得到執(zhí)行,需要注意的是如果一直有優(yōu)先級(jí)高的任務(wù)提交到隊(duì)列里,那么優(yōu)先級(jí)低的任務(wù)可能永遠(yuǎn)不能執(zhí)行。
執(zhí)行時(shí)間不同的任務(wù)可以交給不同規(guī)模的線程池來處理,或者也可以使用優(yōu)先級(jí)隊(duì)列,讓執(zhí)行時(shí)間短的任務(wù)先執(zhí)行。
依賴數(shù)據(jù)庫連接池的任務(wù),因?yàn)榫€程提交 SQL 后需要等待數(shù)據(jù)庫返回結(jié)果,如果等待的時(shí)間越長(zhǎng) CPU 空閑時(shí)間就越長(zhǎng),那么線程數(shù)應(yīng)該設(shè)置越大,這樣才能更好的利用 CPU。
并且,阻塞隊(duì)列最好是使用有界隊(duì)列,如果采用無界隊(duì)列的話,一旦任務(wù)積壓在阻塞隊(duì)列中的話就會(huì)占用過多的內(nèi)存資源,甚至?xí)沟孟到y(tǒng)崩潰。