做者:小傅哥
博客:https://bugstack.cn
Github:https://github.com/fuzhengwei/CodeGuide/wikihtml
沉澱、分享、成長,讓本身和他人都能有所收穫!😄java
人看手機,機器學習!
git
正好是2020年,看到這張圖仍是蠻有意思的。之前小時候總會看到一些科技電影,講到機器人會怎樣怎樣,但沒想到人彷佛被娛樂化的東西,搞成了低頭族、大肚子!程序員
當意識到這一點時,其實很是懷念小時候。放假的早上跑出去,喊上三五個夥伴,要不下河摸摸魚、彈彈玻璃球、打打pia、跳跳房子!一天下來真的不會感受累,但如今若是是放假的一天,你的娛樂安排,不少時候會讓頭很累!github
就像,你有試過學習一天英語頭疼,仍是刷一天抖音頭疼嗎?或者玩一天遊戲與打一天球!若是你意識到了,那麼爭取放下一會手機,適當娛樂,鍛鍊保持個好身體!
面試
謝飛機,小記!
,上次吃虧在線程上,這可能一次坑掉兩次嗎!多線程
謝飛機:你問吧,我準備好了!!!架構
面試官:嗯,線程池狀態是如何設計存儲的?併發
謝飛機:這!下一個,下一個!機器學習
面試官:Worker 的實現類,爲何不使用 ReentrantLock 來實現呢,而是本身繼承AQS?
謝飛機:我...!
面試官:那你簡述下,execute 的執行過程吧!
謝飛機:再見!
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();
這是一段用於建立線程池的例子,相信你已經用了不少次了。
線程池的核心目的就是資源的利用,避免重複建立線程帶來的資源消耗。所以引入一個池化技術的思想,避免重複建立、銷燬帶來的性能開銷。
那麼,接下來咱們就經過實踐的方式分析下這個池子
的構造,看看它是如何處理線程
的。
爲了更好的理解和分析關於線程池的源碼,咱們先來按照線程池的思想,手寫一個很是簡單的線程池。
其實不少時候一段功能代碼的核心主邏輯可能並無多複雜,但爲了讓核心流程順利運行,就須要額外添加不少分支的輔助流程。就像我常說的,爲了保護手才把擦屁屁紙弄那麼大!
關於圖 21-1,這個手寫線程池的實現也很是簡單,只會體現出核心流程,包括:
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("任務編號:" + finalI); }); } } } // 測試結果 任務編號:1 任務編號:0 workQueue.size:8 workQueue.size:8 任務編號:3 workQueue.size:6 任務編號:2 workQueue.size:5 任務編號:5 workQueue.size:4 任務編號:4 workQueue.size:3 任務編號:7 workQueue.size:2 任務編號:6 workQueue.size:1 任務編號:8 任務編號:9 workQueue.size:0 workQueue.size:0
以上,關於線程池的實現仍是很是簡單的,從測試結果上已經能夠把最核心的池化思想體現出來了。主要功能邏輯包括:
ctl
,用於記錄線程池中線程數量。corePoolSize
、maximumPoolSize
,用於限制線程池容量。workQueue
,線程池隊列,也就是那些還不能被及時運行的線程,會被裝入到這個隊列中。execute
,用於提交線程,這個是通用的接口方法。在這個方法裏主要實現的就是,當前提交的線程是加入到worker、隊列仍是放棄。addWorker
,主要是類 Worker
的具體操做,建立並執行線程。這裏還包括了 getTask()
方法,也就是從隊列中不斷的獲取未被執行的線程。好,那麼以上呢,就是這個簡單線程池實現的具體體現。但若是深思熟慮就會發現這裏須要不少完善,好比:線程池狀態呢,不可能一直奔跑呀!?
、線程池的鎖呢,不會有併發問題嗎?
、線程池拒絕後的策略呢?
,這些問題都沒有在主流程解決,也正由於沒有這些流程,因此上面的代碼才更容易理解。
接下來,咱們就開始分析線程池的源碼,與咱們實現的簡單線程池參考對比,會更加容易理解😄!
以圍繞核心類 ThreadPoolExecutor
的實現展開的類之間實現和繼承關係,如圖 21-2 線程池類關係圖。
Executor
、ExecutorService
,定義線程池的基本方法。尤爲是 execute(Runnable command)
提交線程池方法。AbstractExecutorService
,實現了基本通用的接口方法。ThreadPoolExecutor
,是整個線程池最核心的工具類方法,全部的其餘類和接口,爲圍繞這個類來提供各自的功能。Worker
,是任務類,也就是最終執行的線程的方法。RejectedExecutionHandler
,是拒絕策略接口,有四個實現類;AbortPolicy(拋異常方式拒絕)
、DiscardPolicy(直接丟棄)
、DiscardOldestPolicy(丟棄存活時間最長的任務)
、CallerRunsPolicy(誰提交誰執行)
。Executors
,是用於建立咱們經常使用的不一樣策略的線程池,newFixedThreadPool
、newCachedThreadPool
、newScheduledThreadPool
、newSingleThreadExecutor
。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
線程池實現類中,使用 AtomicInteger 類型的 ctl 記錄線程池狀態和線程池數量。在一個類型上記錄多個值,它採用的分割數據區域,高3位
記錄狀態,低29位
存儲線程數量,默認 RUNNING 狀態,線程數爲0個。
圖 22-4 是線程池中的狀態流轉關係,包括以下狀態:
RUNNING
:運行狀態,接受新的任務而且處理隊列中的任務。SHUTDOWN
:關閉狀態(調用了shutdown方法)。不接受新任務,,可是要處理隊列中的任務。STOP
:中止狀態(調用了shutdownNow方法)。不接受新任務,也不處理隊列中的任務,而且要中斷正在處理的任務。TIDYING
:全部的任務都已終止了,workerCount爲0,線程池進入該狀態後會調 terminated() 方法進入TERMINATED 狀態。TERMINATED
:終止狀態,terminated() 方法調用結束後的狀態。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); }
在閱讀這部分源碼的時候,能夠參考咱們本身實現的線程池。其實最終的目的都是同樣的,就是這段被提交的線程,啓動執行
、加入隊列
、決策策略
,這三種方式。
ctl.get()
,取的是記錄線程狀態和線程個數的值,最終須要使用方法 workerCountOf()
,來獲取當前線程數量。`workerCountOf 執行的是 c & CAPACITY 運算corePoolSize
作對比,小於則進行添加線程到任務執行隊列。isRunning(c)
。若是是運行狀態則把不能被執行的線程放入線程隊列中。private boolean addWorker(Runnable firstTask, boolean core)
第一部分、增長線程數量
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 } }
第一部分、建立啓動線程
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;
添加執行任務的流程能夠分爲兩塊看,上面代碼部分是用於記錄線程數量、下面代碼部分是在獨佔鎖裏建立執行線程並啓動。這部分代碼在不看鎖、CAS等操做,那麼就和咱們最開始手寫的線程池基本同樣了
if (rs >= SHUTDOWN &&! (rs == SHUTDOWN &&firstTask == null &&! workQueue.isEmpty()))
,判斷當前線程池狀態,是否爲 SHUTDOWN
、STOP
、TIDYING
、TERMINATED
中的一個。而且當前狀態爲 SHUTDOWN
、且傳入的任務爲 null,同時隊列不爲空。那麼就返回 false。compareAndIncrementWorkerCount
,CAS 操做,增長線程數量,成功就會跳出標記的循環體。runStateOf(c) != rs
,最後是線程池狀態判斷,決定是否循環。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); } }
其實,有了手寫線程池的基礎,到這也就基本瞭解了,線程池在幹嗎。到這最核心的點就是 task.run()
讓線程跑起來。額外再附帶一些其餘流程以下;
beforeExecute
、afterExecute
,線程執行的先後作一些統計信息。processWorkerExit
,若是你感興趣,相似這樣的方法也能夠深刻了解下。在線程退出時候workers作到一些移除處理以及完成任務數等,也很是有意思若是你已經開始閱讀源碼,能夠在 runWorker 方法中,看到這樣一句循環代碼 while (task != null || (task = getTask()) != null)
。這與咱們手寫線程池中操做的方式是同樣的,核心目的就是從隊列中獲取線程方法。
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; } } }
if (rs >= SHUTDOWN ...
,判斷線程是否關閉。wc = workerCountOf(c),wc > corePoolSize
,若是工做線程數超過核心線程數量 corePoolSize
而且 workQueue 不爲空,則增長工做線程。但若是超時未獲取到線程,則會把大於 corePoolSize 的線程銷燬掉。timed
,是 allowCoreThreadTimeOut
得來的。最終 timed
爲 true 時,則經過阻塞隊列的poll方法進行超時控制。keepAliveTime
時間內沒有獲取到任務,則返回null。若是爲false,則阻塞。CPU密集型任務
、IO 密集型任務
時該怎麼配置。另外在Spring中也有本身實現的線程池方法。這些知識點都很是貼近實際操做。