?线程池为什么可以复用,我是蒙圈了。。。
點擊上方?好好學java?,選擇?星標?公眾號
重磅資訊、干貨,第一時間送達
今日推薦:硬剛一周,3W字總結,一年的經驗告訴你如何準備校招!
個人原創100W+訪問量博客:點擊前往,查看更多
本章目錄
?一、線程池狀態
二、execute源碼
三、addworker源碼
四、Worker源碼
五、runworker源碼
六、getTask源碼
七、總結
八、題外話
看了源碼才知道,我還是太菜了。。
一、線程池狀態
首先我們要明確線程池的幾種狀態
1. RUNNING
這個狀態表明線程池處于正常狀態,可以處理任務,可以接受任務
2. SHUTDOWN
這個狀態表明線程池處于正常關閉狀態,不再接受任務,但是可以處理線程池中剩余的任務
3. STOP
這個狀態表明線程池處于停止狀態,不僅不會再接收新任務,并且還會打斷正在執行的任務
4. TIDYING
這個狀態表明線程池已經沒有了任務,所有的任務都被停掉了
5. TERMINATED
線程池徹底終止狀態
他們的狀態轉換圖如下
好了,知道了線程池的幾種狀態和他們是如何轉換的關系之后,我們來看一下
當我們提交一個任務時,線程池到底發生了什么?!
我們平常使用線程池是這樣使用的
for (int i=0;i<10;i++){//創建10個任務Task task = new Task("task" + i);//讓我們自定義的線程池去跑這些任務threadPoolExecutor.execute(task);}我們來看一下 execute里面究竟有什么奇怪的東西?
二、execute源碼
public void execute(Runnable command) {//1.先判斷提交的任務是不是空的if (command == null)throw new NullPointerException();//2.獲得線程池狀態int c = ctl.get();//3.判斷線程池數量是否小于核心線程數if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}//4.線程池數量大于等于核心線程數并且線程池處于Running狀態,這時添加任務至阻塞隊列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);}// 5.走到這里說明添加阻塞隊列失敗,// 創建非核心線程也失敗的話,執行拒絕策略else if (!addWorker(command, false))reject(command);}
然后我們來看3那里,
//3.判斷線程池數量是否小于核心線程數if (workerCountOf(c) < corePoolSize) {//如果小于核心線程數//3.1添加workerif (addWorker(command, true))//添加成功,返回return;//3.2添加失敗,獲取線程池狀態c = ctl.get();}我用頭發想想都知道,線程復用的秘密肯定藏在了addworker里,哦對我沒有頭發 我們再來看一看他里面有什么鬼
三、addworker源碼
private boolean addWorker(Runnable firstTask, boolean core) {//標志位,一會兒會跳過來retry:for (;;) {//判斷線程池狀態int c = ctl.get();int rs = runStateOf(c);//如果狀態非法則返回falseif (rs >= SHUTDOWN && ! (rs == SHUTDOWN && firstTask == null && ! workQueue.isEmpty()))return false;for (;;) {//判斷線程池線程總數量int wc = workerCountOf(c);//如果數量不符合要求則返回falseif (wc >= CAPACITY || wc >= (core ? corePoolSize : maximumPoolSize))return false;//線程池數量加1if (compareAndIncrementWorkerCount(c))//跳到開始 retry處且往下執行,不在進入大循環break retry;//數量增加失敗的話判斷當前線程池狀態若和剛才狀態不一致則繼續執行大循環c = ctl.get(); // Re-read ctlif (runStateOf(c) != rs)continue retry;}}boolean workerStarted = false;boolean workerAdded = false;Worker w = null;try {//將提交的任務封裝進workerw = new Worker(firstTask);//得到worker中的線程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())throw new IllegalThreadStateException();//將worker添加至workers中 (這是個set集合,真正的線程池) workers.add(w);//判斷線程數量int s = workers.size();if (s > largestPoolSize)largestPoolSize = s;//添加worker成功workerAdded = true;}} finally {mainLock.unlock();}if (workerAdded) {//執行worker中的線程t.start();workerStarted = true;}}} finally {if (! workerStarted)addWorkerFailed(w);}return workerStarted;}
其中很重要的一段代碼是
//將提交的任務封裝進workerw = new Worker(firstTask);//得到worker中的線程final Thread t = w.thread;.........//執行worker中的線程t.start();主要我們看這其中的worker是什么東西 (截取了worker中一部分源碼)
四、Worker源碼
private final class Workerextends AbstractQueuedSynchronizerimplements Runnable {private static final long serialVersionUID = 6138294804551838833L;/** Thread this worker is running in. Null if factory fails. */final Thread thread;/** Initial task to run. Possibly null. */Runnable firstTask;/** Per-thread task counter */volatile long completedTasks;Worker(Runnable firstTask) {//線程池狀態設為runningsetState(-1); // inhibit interrupts until runWorker//用戶提交的任務this.firstTask = firstTask; //通過創建一個線程,傳入的this是woker自身 worker繼承了Runnable 那么這個線程在t.start就是調用重寫的run()方法了this.thread = getThreadFactory().newThread(this);}/** Delegates main run loop to outer runWorker */public void run() {runWorker(this);}
我們注意到剛才的t.start(),就是執行woker中的run方法,run方法又執行了runworker() 方法
我們再來看下 runworker() 方法
五、runworker源碼
final void runWorker(Worker w) {//得到當前線程Thread wt = Thread.currentThread();//這是我們提交的任務Runnable task = w.firstTask;w.firstTask = null;w.unlock(); // allow interruptsboolean completedAbruptly = true;try {//線程復用的密碼就在這里,是一個while循環,判斷如果提交的任務不為空或者隊列里有任務的話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 {//任務自己的run方法task.run();} catch (RuntimeException x) {thrown = x; throw x;} catch (Error x) {thrown = x; throw x;} catch (Throwable x) {thrown = x; throw new Error(x);} finally {//執行后的函數,用戶可以自己拓展afterExecute(task, thrown);}} finally {task = null;w.completedTasks++;w.unlock();}}completedAbruptly = false;} finally {//銷毀線程processWorkerExit(w, completedAbruptly);}}
重點來了,我們來看一下 getTask()
六、getTask源碼
private Runnable getTask() {boolean timedOut = false; // Did the last poll() time out?for (;;) {int c = ctl.get();int rs = runStateOf(c);if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {decrementWorkerCount();return null;}//得到線程池線程數量 int wc = workerCountOf(c);// 是否設置超時時間 allowCoreThreadTimeOut默認是false //判斷線程池數量是否大于核心線程數,如果大于的話 timed為trueboolean timed = allowCoreThreadTimeOut || wc > corePoolSize;if ((wc > maximumPoolSize || (timed && timedOut))&& (wc > 1 || workQueue.isEmpty())) {if (compareAndDecrementWorkerCount(c))return null;continue;}try {//這里 timed為ture的時候,采用帶超時時間的獲取元素的方法, 否則采取一直阻塞的方法Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :workQueue.take();//獲取到任務就返回if (r != null)return r;timedOut = true;} catch (InterruptedException retry) {timedOut = false;}}}
由此可見 getTask里面是有超時標志的timed的,我們在第一篇不會吧,就是你把線程池講的這么清楚的?里面說的線程池原理里面講到,若非核心線程空閑keepAliveTime分鐘則銷毀,就是在這里,keepAliveTime時間內未獲取到任務,即為線程空閑狀態,就退出了runWorker中的while循環,進行銷毀線程的操作。
核心線程一定不會銷毀嗎?
我們注意到,這里面有一個allowCoreThreadTimeOut變量,如果他要是為true的話,那么核心線程也是可以銷毀的
真的有核心線程與非核心線程之分嗎?
其實是沒有區別的,他們都是一樣的線程,線程池源碼中并沒有核心線程這個標記,只是有一個核心線程數量,在這個數量之前創建先線程和在這個數量之后創建線程,默認在這個數量之后創建的線程會在keepAliveTime空閑時間內銷毀,我們為了方便記憶,而將其稱為非核心線程
7、總結
大體流程如下圖所示
? ? ??
? ? ?我們向線程池提交任務后,線程池會將我們的任務封裝成一個worker,這個worker里面有要執行的線程t和要執行的任務,這個線程t的主要任務就是t.start,運行runWorker方法,在runWorker方法中,會一直while循環獲取提交的任務。若沒有提交的任務則會看隊列里有沒有任務,獲取隊列任務時就會判斷超時標志是否為true,如果為true的話,則在超時時間內未獲取到任務則返回null,然后銷毀當前線程,否則一直等待到獲取到任務為止,不銷毀線程,這樣就做到了線程復用。
最后,給大家準備了一套算法學習教程,從小白到大神,都是這樣走過來的,建議學習一下,拿走不謝! 下載方式1.?首先掃描下方二維碼2.?后臺回復「A110」即可獲取總結
以上是生活随笔為你收集整理的?线程池为什么可以复用,我是蒙圈了。。。的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: RocketMQ在面试中那些常见问题及答
- 下一篇: 5年前的Dubbo,2年前的Spring