手寫(xiě)線程池,對(duì)照學(xué)習(xí)ThreadPoolExecutor線程池實(shí)現(xiàn)原理!
本文轉(zhuǎn)載自微信公眾號(hào)「bugstack蟲(chóng)洞?!?,作者小傅哥。轉(zhuǎn)載本文請(qǐng)聯(lián)系bugstack蟲(chóng)洞棧公眾號(hào)。
目錄
一、前言
二、面試題
三、線程池講解
- 1. 先看個(gè)例子
- 2. 手寫(xiě)一個(gè)線程池
- 3. 線程池源碼分析
四、總結(jié)
一、前言
人看手機(jī),機(jī)器學(xué)習(xí)!
正好是2020年,看到這張圖還是蠻有意思的。以前小時(shí)候總會(huì)看到一些科技電影,講到機(jī)器人會(huì)怎樣怎樣,但沒(méi)想到人似乎被娛樂(lè)化的東西,搞成了低頭族、大肚子!
當(dāng)意識(shí)到這一點(diǎn)時(shí),其實(shí)非常懷念小時(shí)候。放假的早上跑出去,喊上三五個(gè)伙伴,要不下河摸摸魚(yú)、彈彈玻璃球、打打pia、跳跳房子!一天下來(lái)真的不會(huì)感覺(jué)累,但現(xiàn)在如果是放假的一天,你的娛樂(lè)安排,很多時(shí)候會(huì)讓頭很累!
「就像」,你有試過(guò)學(xué)習(xí)一天英語(yǔ)頭疼,還是刷一天抖音頭疼嗎?或者玩一天游戲與打一天球!如果你意識(shí)到了,那么爭(zhēng)取放下一會(huì)手機(jī),適當(dāng)娛樂(lè),鍛煉保持個(gè)好身體!
二、面試題
謝飛機(jī),小記!,上次吃虧在線程上,這次可能一次坑掉兩次了!
「謝飛機(jī)」:你問(wèn)吧,我準(zhǔn)備好了!!!
「面試官」:嗯,線程池狀態(tài)是如何設(shè)計(jì)存儲(chǔ)的?
「謝飛機(jī)」:這!下一個(gè),下一個(gè)!
「面試官」:Worker 的實(shí)現(xiàn)類(lèi),為什么不使用 ReentrantLock 來(lái)實(shí)現(xiàn)呢,而是自己繼承AQS?
「謝飛機(jī)」:我...!
「面試官」:那你簡(jiǎn)述下,execute 的執(zhí)行過(guò)程吧!
「謝飛機(jī)」:再見(jiàn)!
三、線程池講解
1. 先看個(gè)例子
- ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 10, 0L, TimeUnit.MILLISECONDS, new ArrayBlockingQueue<>(10));
- threadPoolExecutor.execute(() -> {
- System.out.println("Hi 線程池!");
- });
- threadPoolExecutor.shutdown();
- // Executors.newFixedThreadPool(10);
- // Executors.newCachedThreadPool();
- // Executors.newScheduledThreadPool(10);
- // Executors.newSingleThreadExecutor();
這是一段用于創(chuàng)建線程池的例子,相信你已經(jīng)用了很多次了。
線程池的核心目的就是資源的利用,避免重復(fù)創(chuàng)建線程帶來(lái)的資源消耗。因此引入一個(gè)池化技術(shù)的思想,避免重復(fù)創(chuàng)建、銷(xiāo)毀帶來(lái)的性能開(kāi)銷(xiāo)。
「那么」,接下來(lái)我們就通過(guò)實(shí)踐的方式分析下這個(gè)池子的構(gòu)造,看看它是如何處理線程的。
2. 手寫(xiě)一個(gè)線程池
2.1 實(shí)現(xiàn)流程
為了更好的理解和分析關(guān)于線程池的源碼,我們先來(lái)按照線程池的思想,手寫(xiě)一個(gè)非常簡(jiǎn)單的線程池。
其實(shí)很多時(shí)候一段功能代碼的核心主邏輯可能并沒(méi)有多復(fù)雜,但為了讓核心流程順利運(yùn)行,就需要額外添加很多分支的輔助流程。就像我常說(shuō)的,為了保護(hù)手才把擦屁屁紙弄那么大!
圖 21-1 線程池簡(jiǎn)化流程
關(guān)于圖 21-1,這個(gè)手寫(xiě)線程池的實(shí)現(xiàn)也非常簡(jiǎn)單,只會(huì)體現(xiàn)出核心流程,包括:
有n個(gè)一直在運(yùn)行的線程,相當(dāng)于我們創(chuàng)建線程池時(shí)允許的線程池大小。
把線程提交給線程池運(yùn)行。
如果運(yùn)行線程池已滿,則把線程放入隊(duì)列中。
最后當(dāng)有空閑時(shí),則獲取隊(duì)列中線程進(jìn)行運(yùn)行。
2.2 實(shí)現(xiàn)代碼
- public class ThreadPoolTrader implements Executor {
- private final AtomicInteger ctl = new AtomicInteger(0);
- private volatile int corePoolSize;
- private volatile int maximumPoolSize;
- private final BlockingQueue<Runnable> workQueue;
- public ThreadPoolTrader(int corePoolSize, int maximumPoolSize, BlockingQueue<Runnable> workQueue) {
- this.corePoolSize = corePoolSize;
- this.maximumPoolSize = maximumPoolSize;
- this.workQueue = workQueue;
- }
- @Override
- public void execute(Runnable command) {
- int c = ctl.get();
- if (c < corePoolSize) {
- if (!addWorker(command)) {
- reject();
- }
- return;
- }
- if (!workQueue.offer(command)) {
- if (!addWorker(command)) {
- reject();
- }
- }
- }
- private boolean addWorker(Runnable firstTask) {
- if (ctl.get() >= maximumPoolSize) return false;
- Worker worker = new Worker(firstTask);
- worker.thread.start();
- ctl.incrementAndGet();
- return true;
- }
- private final class Worker implements Runnable {
- final Thread thread;
- Runnable firstTask;
- public Worker(Runnable firstTask) {
- this.thread = new Thread(this);
- this.firstTask = firstTask;
- }
- @Override
- public void run() {
- Runnable task = firstTask;
- try {
- while (task != null || (task = getTask()) != null) {
- task.run();
- if (ctl.get() > maximumPoolSize) {
- break;
- }
- task = null;
- }
- } finally {
- ctl.decrementAndGet();
- }
- }
- private Runnable getTask() {
- for (; ; ) {
- try {
- System.out.println("workQueue.size:" + workQueue.size());
- return workQueue.take();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
- }
- private void reject() {
- throw new RuntimeException("Error!ctl.count:" + ctl.get() + " workQueue.size:" + workQueue.size());
- }
- public static void main(String[] args) {
- ThreadPoolTrader threadPoolTrader = new ThreadPoolTrader(2, 2, new ArrayBlockingQueue<Runnable>(10));
- for (int i = 0; i < 10; i++) {
- int finalI = i;
- threadPoolTrader.execute(() -> {
- try {
- Thread.sleep(1500);
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- System.out.println("任務(wù)編號(hào):" + finalI);
- });
- }
- }
- }
- // 測(cè)試結(jié)果
- 任務(wù)編號(hào):1
- 任務(wù)編號(hào):0
- workQueue.size:8
- workQueue.size:8
- 任務(wù)編號(hào):3
- workQueue.size:6
- 任務(wù)編號(hào):2
- workQueue.size:5
- 任務(wù)編號(hào):5
- workQueue.size:4
- 任務(wù)編號(hào):4
- workQueue.size:3
- 任務(wù)編號(hào):7
- workQueue.size:2
- 任務(wù)編號(hào):6
- workQueue.size:1
- 任務(wù)編號(hào):8
- 任務(wù)編號(hào):9
- workQueue.size:0
- workQueue.size:0
「以上」,關(guān)于線程池的實(shí)現(xiàn)還是非常簡(jiǎn)單的,從測(cè)試結(jié)果上已經(jīng)可以把最核心的池化思想體現(xiàn)出來(lái)了。主要功能邏輯包括:
- ctl,用于記錄線程池中線程數(shù)量。
- corePoolSize、maximumPoolSize,用于限制線程池容量。
- workQueue,線程池隊(duì)列,也就是那些還不能被及時(shí)運(yùn)行的線程,會(huì)被裝入到這個(gè)隊(duì)列中。
- execute,用于提交線程,這個(gè)是通用的接口方法。在這個(gè)方法里主要實(shí)現(xiàn)的就是,當(dāng)前提交的線程是加入到worker、隊(duì)列還是放棄。
- addWorker,主要是類(lèi) Worker 的具體操作,創(chuàng)建并執(zhí)行線程。這里還包括了 getTask() 方法,也就是從隊(duì)列中不斷的獲取未被執(zhí)行的線程。
「好」,那么以上呢,就是這個(gè)簡(jiǎn)單線程池實(shí)現(xiàn)的具體體現(xiàn)。但如果深思熟慮就會(huì)發(fā)現(xiàn)這里需要很多完善,比如:線程池狀態(tài)呢,不可能一直奔跑呀!?、線程池的鎖呢,不會(huì)有并發(fā)問(wèn)題嗎?、線程池拒絕后的策略呢?,這些問(wèn)題都沒(méi)有在主流程解決,也正因?yàn)闆](méi)有這些流程,所以上面的代碼才更容易理解。
接下來(lái),我們就開(kāi)始分析線程池的源碼,與我們實(shí)現(xiàn)的簡(jiǎn)單線程池參考對(duì)比,會(huì)更加容易理解??!
3. 線程池源碼分析
3.1 線程池類(lèi)關(guān)系圖
圖 21-2 線程池類(lèi)關(guān)系圖
以圍繞核心類(lèi) ThreadPoolExecutor 的實(shí)現(xiàn)展開(kāi)的類(lèi)之間實(shí)現(xiàn)和繼承關(guān)系,如圖 21-2 線程池類(lèi)關(guān)系圖。
- 接口 Executor、ExecutorService,定義線程池的基本方法。尤其是 execute(Runnable command) 提交線程池方法。
- 抽象類(lèi) AbstractExecutorService,實(shí)現(xiàn)了基本通用的接口方法。
- ThreadPoolExecutor,是整個(gè)線程池最核心的工具類(lèi)方法,所有的其他類(lèi)和接口,為圍繞這個(gè)類(lèi)來(lái)提供各自的功能。
- Worker,是任務(wù)類(lèi),也就是最終執(zhí)行的線程的方法。
- RejectedExecutionHandler,是拒絕策略接口,有四個(gè)實(shí)現(xiàn)類(lèi);AbortPolicy(拋異常方式拒絕)、DiscardPolicy(直接丟棄)、DiscardOldestPolicy(丟棄存活時(shí)間最長(zhǎng)的任務(wù))、CallerRunsPolicy(誰(shuí)提交誰(shuí)執(zhí)行)。
- Executors,是用于創(chuàng)建我們常用的不同策略的線程池,newFixedThreadPool、newCachedThreadPool、newScheduledThreadPool、newSingleThreadExecutor。
3.2 高3位與低29位
圖 22-3 線程狀態(tài),高3位與低29位
- private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
- private static final int COUNT_BITS = Integer.SIZE - 3;
- private static final int CAPACITY = (1 << COUNT_BITS) - 1;
- private static final int RUNNING = -1 << COUNT_BITS;
- private static final int SHUTDOWN = 0 << COUNT_BITS;
- private static final int STOP = 1 << COUNT_BITS;
- private static final int TIDYING = 2 << COUNT_BITS;
- private static final int TERMINATED = 3 << COUNT_BITS;
在 ThreadPoolExecutor 線程池實(shí)現(xiàn)類(lèi)中,使用 AtomicInteger 類(lèi)型的 ctl 記錄線程池狀態(tài)和線程池?cái)?shù)量。在一個(gè)類(lèi)型上記錄多個(gè)值,它采用的分割數(shù)據(jù)區(qū)域,高3位記錄狀態(tài),低29位存儲(chǔ)線程數(shù)量,默認(rèn) RUNNING 狀態(tài),線程數(shù)為0個(gè)。
3.2 線程池狀態(tài)
圖 22-4 線程池狀態(tài)流轉(zhuǎn)
圖 22-4 是線程池中的狀態(tài)流轉(zhuǎn)關(guān)系,包括如下?tīng)顟B(tài):
- RUNNING:運(yùn)行狀態(tài),接受新的任務(wù)并且處理隊(duì)列中的任務(wù)。
- SHUTDOWN:關(guān)閉狀態(tài)(調(diào)用了shutdown方法)。不接受新任務(wù),,但是要處理隊(duì)列中的任務(wù)。
- STOP:停止?fàn)顟B(tài)(調(diào)用了shutdownNow方法)。不接受新任務(wù),也不處理隊(duì)列中的任務(wù),并且要中斷正在處理的任務(wù)。
- TIDYING:所有的任務(wù)都已終止了,workerCount為0,線程池進(jìn)入該狀態(tài)后會(huì)調(diào) terminated() 方法進(jìn)入TERMINATED 狀態(tài)。
- TERMINATED:終止?fàn)顟B(tài),terminated() 方法調(diào)用結(jié)束后的狀態(tài)。
3.3 提交線程(execute)
圖 22-5 提交線程流程圖
- public void execute(Runnable command) {
- if (command == null)
- throw new NullPointerException();
- int c = ctl.get();
- if (workerCountOf(c) < corePoolSize) {
- if (addWorker(command, true))
- return;
- c = ctl.get();
- }
- 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);
- }
- else if (!addWorker(command, false))
- reject(command);
- }
在閱讀這部分源碼的時(shí)候,可以參考我們自己實(shí)現(xiàn)的線程池。其實(shí)最終的目的都是一樣的,就是這段被提交的線程,啟動(dòng)執(zhí)行、加入隊(duì)列、決策策略,這三種方式。
- ctl.get(),取的是記錄線程狀態(tài)和線程個(gè)數(shù)的值,最終需要使用方法 workerCountOf(),來(lái)獲取當(dāng)前線程數(shù)量。`workerCountOf 執(zhí)行的是 c & CAPACITY 運(yùn)算
- 根據(jù)當(dāng)前線程池中線程數(shù)量,與核心線程數(shù) corePoolSize 做對(duì)比,小于則進(jìn)行添加線程到任務(wù)執(zhí)行隊(duì)列。
- 如果說(shuō)此時(shí)線程數(shù)已滿,那么則需要判斷線程池是否為運(yùn)行狀態(tài) isRunning(c)。如果是運(yùn)行狀態(tài)則把不能被執(zhí)行的線程放入線程隊(duì)列中。
- 放入線程隊(duì)列以后,還需要重新判斷線程是否運(yùn)行以及移除操作,如果非運(yùn)行且移除,則進(jìn)行拒絕策略。否則判斷線程數(shù)量為0后添加新線程。
- 最后就是再次嘗試添加任務(wù)執(zhí)行,此時(shí)方法 addWorker 的第二個(gè)入?yún)⑹? false,最終會(huì)影響添加執(zhí)行任務(wù)數(shù)量判斷。如果添加失敗則進(jìn)行拒絕策略。
3.5 添加執(zhí)行任務(wù)(addWorker)
圖 22-6 添加執(zhí)行任務(wù)邏輯流程
「private boolean addWorker(Runnable firstTask, boolean core)」
「第一部分、增加線程數(shù)量」
- retry:
- for (;;) {
- int c = ctl.get();
- int rs = runStateOf(c);
- // Check if queue empty only if necessary.
- if (rs >= SHUTDOWN &&
- ! (rs == SHUTDOWN &&
- firstTask == null &&
- ! workQueue.isEmpty()))
- return false;
- for (;;) {
- int wc = workerCountOf(c);
- if (wc >= CAPACITY ||
- wc >= (core ? corePoolSize : maximumPoolSize))
- return false;
- if (compareAndIncrementWorkerCount(c))
- break retry;
- c = ctl.get(); // Re-read ctl
- if (runStateOf(c) != rs)
- continue retry;
- // else CAS failed due to workerCount change; retry inner loop
- }
- }
「第一部分、創(chuàng)建啟動(dòng)線程」
- boolean workerStarted = false;
- boolean workerAdded = false;
- Worker w = null;
- try {
- w = new Worker(firstTask);
- final Thread t = w.thread;
- if (t != null) {
- final ReentrantLock mainLock = this.mainLock;
- mainLock.lock();
- try {
- int rs = runStateOf(ctl.get());
- if (rs < SHUTDOWN ||
- (rs == SHUTDOWN && firstTask == null)) {
- if (t.isAlive()) // precheck that t is startable
- throw new IllegalThreadStateException();
- workers.add(w);
- int s = workers.size();
- if (s > largestPoolSize)
- largestPoolSize = s;
- workerAdded = true;
- }
- } finally {
- mainLock.unlock();
- }
- if (workerAdded) {
- t.start();
- workerStarted = true;
- }
- }
- } finally {
- if (! workerStarted)
- addWorkerFailed(w);
- }
- return workerStarted;
添加執(zhí)行任務(wù)的流程可以分為兩塊看,上面代碼部分是用于記錄線程數(shù)量、下面代碼部分是在獨(dú)占鎖里創(chuàng)建執(zhí)行線程并啟動(dòng)。這部分代碼在不看鎖、CAS等操作,那么就和我們最開(kāi)始手寫(xiě)的線程池基本一樣了
- if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty())),判斷當(dāng)前線程池狀態(tài),是否為 SHUTDOWN、STOP、TIDYING、TERMINATED中的一個(gè)。并且當(dāng)前狀態(tài)為 SHUTDOWN、且傳入的任務(wù)為 null,同時(shí)隊(duì)列不為空。那么就返回 false。
- compareAndIncrementWorkerCount,CAS 操作,增加線程數(shù)量,成功就會(huì)跳出標(biāo)記的循環(huán)體。
- runStateOf(c) != rs,最后是線程池狀態(tài)判斷,決定是否循環(huán)。
- 在線程池?cái)?shù)量記錄成功后,則需要進(jìn)入加鎖環(huán)節(jié),創(chuàng)建執(zhí)行線程,并記錄狀態(tài)。在最后如果判斷沒(méi)有啟動(dòng)成功,則需要執(zhí)行 addWorkerFailed 方法,剔除到線程方法等操作。
3.6 執(zhí)行線程(runWorker)
- final void runWorker(Worker w) {
- Thread wt = Thread.currentThread();
- Runnable task = w.firstTask;
- w.firstTask = null;
- w.unlock(); // 允許中斷
- boolean completedAbruptly = true;
- try {
- while (task != null || (task = getTask()) != null)
- w.lock();
- if ((runStateAtLeast(ctl.get(), STOP) ||
- (Thread.interrupted() &&
- runStateAtLeast(ctl.get(), STOP))) &&
- !wt.isInterrupted())
- wt.interrupt();
- try {
- beforeExecute(wt, task);
- Throwable thrown = null;
- try {
- task.run();
- } finally {
- afterExecute(task, thrown);
- }
- } finally {
- task = null;
- w.completedTasks++;
- w.unlock();
- }
- }
- completedAbruptly = false;
- } finally {
- processWorkerExit(w, completedAbruptly);
- }
- }
「其實(shí)」,有了手寫(xiě)線程池的基礎(chǔ),到這也就基本了解了,線程池在干嘛。到這最核心的點(diǎn)就是 task.run() 讓線程跑起來(lái)。額外再附帶一些其他流程如下;
- beforeExecute、afterExecute,線程執(zhí)行的前后做一些統(tǒng)計(jì)信息。
- 另外這里的鎖操作是 Worker 繼承 AQS 自己實(shí)現(xiàn)的不可重入的獨(dú)占鎖。
- processWorkerExit,如果你感興趣,類(lèi)似這樣的方法也可以深入了解下。在線程退出時(shí)候workers做到一些移除處理以及完成任務(wù)數(shù)等,也非常有意思
3.7 隊(duì)列獲取任務(wù)(getTask)
如果你已經(jīng)開(kāi)始閱讀源碼,可以在 runWorker 方法中,看到這樣一句循環(huán)代碼 while (task != null || (task = getTask()) != null)。這與我們手寫(xiě)線程池中操作的方式是一樣的,核心目的就是從隊(duì)列中獲取線程方法。
- private Runnable getTask() {
- boolean timedOut = false; // Did the last poll() time out?
- for (;;) {
- int c = ctl.get();
- int rs = runStateOf(c);
- // Check if queue empty only if necessary.
- if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
- decrementWorkerCount();
- return null;
- }
- int wc = workerCountOf(c);
- // Are workers subject to culling?
- boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
- if ((wc > maximumPoolSize || (timed && timedOut))
- && (wc > 1 || workQueue.isEmpty())) {
- if (compareAndDecrementWorkerCount(c))
- return null;
- continue;
- }
- try {
- Runnable r = timed ?
- workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
- workQueue.take();
- if (r != null)
- return r;
- timedOut = true;
- } catch (InterruptedException retry) {
- timedOut = false;
- }
- }
- }
- getTask 方法從阻塞隊(duì)列中獲取等待被執(zhí)行的任務(wù),也就是一條條往出拿線程方法。
- if (rs >= SHUTDOWN ...,判斷線程是否關(guān)閉。
- wc = workerCountOf(c),wc > corePoolSize,如果工作線程數(shù)超過(guò)核心線程數(shù)量 corePoolSize 并且 workQueue 不為空,則增加工作線程。但如果超時(shí)未獲取到線程,則會(huì)把大于 corePoolSize 的線程銷(xiāo)毀掉。
- timed,是 allowCoreThreadTimeOut 得來(lái)的。最終 timed 為 true 時(shí),則通過(guò)阻塞隊(duì)列的poll方法進(jìn)行超時(shí)控制。
- 如果在 keepAliveTime 時(shí)間內(nèi)沒(méi)有獲取到任務(wù),則返回null。如果為false,則阻塞。
四、總結(jié)
這一章節(jié)并沒(méi)有完全把線程池的所有知識(shí)點(diǎn)都介紹完,否則一篇內(nèi)容會(huì)有些臃腫。在這一章節(jié)我們從手寫(xiě)線程池開(kāi)始,逐步的分析這些代碼在Java的線程池中是如何實(shí)現(xiàn)的,涉及到的知識(shí)點(diǎn)也幾乎是我們以前介紹過(guò)的內(nèi)容,包括:隊(duì)列、CAS、AQS、重入鎖、獨(dú)占鎖等內(nèi)容。所以這些知識(shí)也基本是環(huán)環(huán)相扣的,最好有一些根基否則會(huì)有些不好理解。
除了本章介紹的,我們還沒(méi)有講到線程的銷(xiāo)毀過(guò)程、四種線程池方法的選擇和使用、以及在CPU密集型任務(wù)、IO 密集型任務(wù)時(shí)該怎么配置。另外在Spring中也有自己實(shí)現(xiàn)的線程池方法。這些知識(shí)點(diǎn)都非常貼近實(shí)際操作。
好了,今天的內(nèi)容先扯到這,后續(xù)的內(nèi)容陸續(xù)完善。如果以上內(nèi)容有錯(cuò)字、流程缺失、或者不好理解以及描述錯(cuò)誤,歡迎留言?;ハ鄬W(xué)習(xí)、互相進(jìn)步。