詳解Java線程池如何統(tǒng)計(jì)線程空閑時(shí)間
背景介紹
你剛從學(xué)校畢業(yè)后,到新公司實(shí)習(xí),試用期又被畢業(yè),然后你又不得不出來(lái)面試,好在面試的時(shí)候碰到個(gè)美女面試官!
面試官: 小伙子,我看你簡(jiǎn)歷上寫(xiě)的項(xiàng)目中用到了線程池,你知道線程池是怎樣實(shí)現(xiàn)復(fù)用線程的?
這面試官是不是想坑我?是不是擺明了不讓我通過(guò)?
難道你不應(yīng)該問(wèn)線程池有哪些核心參數(shù)?每個(gè)參數(shù)具體作用是什么?
往線程池中不斷提交任務(wù),線程池的處理流程是什么?
這些才是你應(yīng)該問(wèn)的,這些八股文我已經(jīng)背熟了,你不問(wèn),瞎問(wèn)什么復(fù)用線程?
幸虧我看了一燈的八股文,聽(tīng)我給你背一遍!
我: 線程池復(fù)用線程的邏輯很簡(jiǎn)單,就是在線程啟動(dòng)后,通過(guò)while死循環(huán),不斷從阻塞隊(duì)列中拉取任務(wù),從而達(dá)到了復(fù)用線程的目的。
具體源碼如下:
// 線程執(zhí)行入口 public void run() { runWorker(this); } // 線程運(yùn)行核心方法 final void runWorker(Worker w) { Thread wt = Thread.currentThread(); Runnable task = w.firstTask; w.firstTask = null; w.unlock(); boolean completedAbruptly = true; try { // 1. 使用while死循環(huán),不斷從阻塞隊(duì)列中拉取任務(wù) while (task != null || (task = getTask()) != null) { // 加鎖,保證thread不被其他線程中斷(除非線程池被中斷) w.lock(); // 2. 校驗(yàn)線程池狀態(tài),是否需要中斷當(dāng)前線程 if ((runStateAtLeast(ctl.get(), STOP) || (Thread.interrupted() && runStateAtLeast(ctl.get(), STOP))) && !wt.isInterrupted()) wt.interrupt(); try { beforeExecute(wt, task); Throwable thrown = null; try { // 3. 執(zhí)行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); } }
runWorker方法邏輯很簡(jiǎn)單,就是不斷從阻塞隊(duì)列中拉取任務(wù)并執(zhí)行。
面試官: 小伙子,有點(diǎn)東西。我們都知道線程池會(huì)回收超過(guò)空閑時(shí)間的線程,那么線程池是怎么統(tǒng)計(jì)線程的空閑時(shí)間的?
美女面試官的問(wèn)題真刁鉆,讓人頭疼啊!這問(wèn)的也太深了吧!
沒(méi)看過(guò)源碼的話,真不好回答。
我: 嗯...,可能是有個(gè)監(jiān)控線程在后臺(tái)不停的統(tǒng)計(jì)每個(gè)線程的空閑時(shí)間,看到線程的空閑時(shí)間超過(guò)閾值的時(shí)候,就回收掉。
面試官: 小伙子,你的想法挺不錯(cuò),邏輯很嚴(yán)謹(jǐn),你確定線程池內(nèi)部是這么實(shí)現(xiàn)的嗎?
問(wèn)得我有點(diǎn)不自信了,沒(méi)看過(guò)源碼不能瞎蒙。
我還是去瞅一眼一燈寫(xiě)的八股文吧。
我: 這個(gè)我知道,線程池統(tǒng)計(jì)線程的空閑時(shí)間的實(shí)現(xiàn)邏輯很簡(jiǎn)單。
阻塞隊(duì)列(BlockingQueue)提供了一個(gè)poll(time, unit)
方法用來(lái)拉取數(shù)據(jù),
作用就是: 當(dāng)隊(duì)列為空時(shí),會(huì)阻塞指定時(shí)間,然后返回null。
線程池就是就是利用阻塞隊(duì)列的這個(gè)方法,如果在指定時(shí)間內(nèi)拉取不到任務(wù),就表示該線程的存活時(shí)間已經(jīng)超過(guò)閾值了,就要被回收了。
具體源碼如下:
// 從阻塞隊(duì)列中拉取任務(wù) private Runnable getTask() { boolean timedOut = false; for (; ; ) { int c = ctl.get(); int rs = runStateOf(c); // 1. 如果線程池已經(jīng)停了,或者阻塞隊(duì)列是空,就回收當(dāng)前線程 if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { decrementWorkerCount(); return null; } int wc = workerCountOf(c); // 2. 再次判斷是否需要回收線程 boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; if ((wc > maximumPoolSize || (timed && timedOut)) && (wc > 1 || workQueue.isEmpty())) { if (compareAndDecrementWorkerCount(c)) return null; continue; } try { // 3. 在指定時(shí)間內(nèi),從阻塞隊(duì)列中拉取任務(wù) Runnable r = timed ? workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take(); if (r != null) return r; // 4. 如果沒(méi)有拉取到任務(wù),就標(biāo)識(shí)該線程已超時(shí),然后就被回收 timedOut = true; } catch (InterruptedException retry) { timedOut = false; } } }
面試官: 小伙子,可以啊,你是懂線程池源碼的。再問(wèn)你個(gè)問(wèn)題,如果線程池拋異常了,也沒(méi)有try/catch,會(huì)發(fā)生什么?
美女面試官你這是準(zhǔn)備打破砂鍋問(wèn)到底,鐵了心不讓我過(guò),是吧?
我的代碼風(fēng)格是很嚴(yán)謹(jǐn)?shù)模l(shuí)寫(xiě)的業(yè)務(wù)代碼不try/catch,也沒(méi)遇到過(guò)這種情況。
讓我再看一下一燈總結(jié)的八股文吧。
我: 有了,線程池中的代碼如果拋異常了,也沒(méi)有try/catch,會(huì)從線程池中刪除這個(gè)異常線程,并創(chuàng)建一個(gè)新線程。
不信的話,我們可以測(cè)試驗(yàn)證一下:
/** * @author 一燈架構(gòu) * @apiNote 線程池示例 **/ public class ThreadPoolDemo { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); // 1. 創(chuàng)建一個(gè)單個(gè)線程的線程池 ExecutorService executorService = Executors.newSingleThreadExecutor(); // 2. 往線程池中提交3個(gè)任務(wù) for (int i = 0; i < 3; i++) { executorService.execute(() -> { System.out.println(Thread.currentThread().getName() + " 關(guān)注公眾號(hào):一燈架構(gòu)"); throw new RuntimeException("拋異常了!"); }); } // 3. 關(guān)閉線程池 executorService.shutdown(); } }
輸出結(jié)果:
pool-1-thread-1 關(guān)注公眾號(hào):一燈架構(gòu)
pool-1-thread-2 關(guān)注公眾號(hào):一燈架構(gòu)
pool-1-thread-3 關(guān)注公眾號(hào):一燈架構(gòu)
Exception in thread "pool-1-thread-1" java.lang.RuntimeException: 拋異常了!
at com.yideng.SynchronousQueueDemo.lambda$main$0(ThreadPoolDemo.java:21)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-2" java.lang.RuntimeException: 拋異常了!
at com.yideng.SynchronousQueueDemo.lambda$main$0(ThreadPoolDemo.java:21)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
Exception in thread "pool-1-thread-3" java.lang.RuntimeException: 拋異常了!
at com.yideng.SynchronousQueueDemo.lambda$main$0(ThreadPoolDemo.java:21)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)
at java.lang.Thread.run(Thread.java:748)
從輸出結(jié)果中可以看出,線程名稱并不是同一個(gè),而是累加的,說(shuō)明原線程已經(jīng)被回收,新建了個(gè)線程。
我們?cè)倏匆幌略创a,驗(yàn)證一下:
// 線程拋異常后,退出邏輯 private void processWorkerExit(ThreadPoolExecutor.Worker w, boolean completedAbruptly) { if (completedAbruptly) decrementWorkerCount(); final ReentrantLock mainLock = this.mainLock; mainLock.lock(); try { completedTaskCount += w.completedTasks; // 1. 從工作線程中刪除當(dāng)前線程 workers.remove(w); } finally { mainLock.unlock(); } // 2. 中斷當(dāng)前線程 tryTerminate(); int c = ctl.get(); if (runStateLessThan(c, STOP)) { if (!completedAbruptly) { int min = allowCoreThreadTimeOut ? 0 : corePoolSize; if (min == 0 && !workQueue.isEmpty()) min = 1; if (workerCountOf(c) >= min) return; // replacement not needed } // 3. 新建一個(gè)線程 addWorker(null, false); } }
如果想統(tǒng)一處理異常,可以自定義線程創(chuàng)建工廠,在工廠里面設(shè)置異常處理邏輯。
/** * @author 一燈架構(gòu) * @apiNote 線程池示例 **/ public class ThreadPoolDemo { public static void main(String[] args) { List<Integer> list = new ArrayList<>(); // 1. 創(chuàng)建一個(gè)單個(gè)線程的線程池 ExecutorService executorService = Executors.newSingleThreadExecutor(runnable -> { // 2. 自定義線程創(chuàng)建工廠,并設(shè)置異常處理邏輯 Thread thread = new Thread(runnable); thread.setUncaughtExceptionHandler((t, e) -> { System.out.println("捕獲到異常:" + e.getMessage()); }); return thread; }); // 3. 往線程池中提交3個(gè)任務(wù) for (int i = 0; i < 3; i++) { executorService.execute(() -> { System.out.println(Thread.currentThread().getName() + " 關(guān)注公眾號(hào):一燈架構(gòu)"); throw new RuntimeException("拋異常了!"); }); } // 4. 關(guān)閉線程池 executorService.shutdown(); } }
輸出結(jié)果:
Thread-0 關(guān)注公眾號(hào):一燈架構(gòu)
捕獲到異常:拋異常了!
Thread-1 關(guān)注公眾號(hào):一燈架構(gòu)
捕獲到異常:拋異常了!
Thread-2 關(guān)注公眾號(hào):一燈架構(gòu)
捕獲到異常:拋異常了!
以上就是詳解Java線程池如何統(tǒng)計(jì)線程空閑時(shí)間的詳細(xì)內(nèi)容,更多關(guān)于Java統(tǒng)計(jì)線程空閑時(shí)間的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
- Java實(shí)現(xiàn)手寫(xiě)乞丐版線程池的示例代碼
- Java實(shí)現(xiàn)手寫(xiě)一個(gè)線程池的示例代碼
- 一文了解Java?線程池的正確使用姿勢(shì)
- Java手寫(xiě)線程池之向JDK線程池進(jìn)發(fā)
- Java線程池源碼的深度解析
- 一篇文章帶你搞懂Java線程池實(shí)現(xiàn)原理
- Java中的異步與線程池解讀
- 詳解Java線程池隊(duì)列中的延遲隊(duì)列DelayQueue
- 一文帶你弄懂Java中線程池的原理
- java 線程池的實(shí)現(xiàn)原理、優(yōu)點(diǎn)與風(fēng)險(xiǎn)、以及4種線程池實(shí)現(xiàn)
相關(guān)文章
String轉(zhuǎn)double失去精度問(wèn)題及解決
這篇文章主要介紹了關(guān)于String轉(zhuǎn)double失去精度問(wèn)題及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2023-07-07Java客戶端通過(guò)HTTPS連接到Easysearch實(shí)現(xiàn)過(guò)程
這篇文章主要為大家介紹了Java客戶端通過(guò)HTTPS連接到Easysearch實(shí)現(xiàn)過(guò)程詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪2023-11-11解決Springboot @WebFilter攔截器未生效問(wèn)題
這篇文章主要介紹了解決Springboot @WebFilter攔截器未生效問(wèn)題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧2020-10-10java定時(shí)任務(wù)的實(shí)現(xiàn)方式
這篇文章主要介紹了java定時(shí)任務(wù)的實(shí)現(xiàn)方式,在應(yīng)用里經(jīng)常都有用到在后臺(tái)跑定時(shí)任務(wù)的需求,如何進(jìn)行java定時(shí)任務(wù),本文為大家進(jìn)行講解,感興趣的小伙伴們可以參考一下2016-02-02IDEA安裝部署Alibaba Cloud Toolkit的實(shí)現(xiàn)步驟
Alibaba Cloud Toolkit是阿里云針對(duì)IDE平臺(tái)為開(kāi)發(fā)者提供的一款插件,本文主要介紹了IDEA安裝部署Alibaba Cloud Toolkit的實(shí)現(xiàn)步驟,具有一定的參考價(jià)值,感興趣的可以了解一下2023-08-08Java中BigDecimal除法使用不當(dāng)導(dǎo)致精度問(wèn)題
本文主要介紹了Java中BigDecimal除法使用不當(dāng)導(dǎo)致精度問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-11-11Java中消息隊(duì)列任務(wù)的平滑關(guān)閉詳解
對(duì)于消息隊(duì)列的監(jiān)聽(tīng),我們一般使用Java寫(xiě)一個(gè)獨(dú)立的程序,在Linux服務(wù)器上運(yùn)行。程序啟動(dòng)后,通過(guò)消息隊(duì)列客戶端接收消息,放入一個(gè)線程池進(jìn)行異步處理,并發(fā)的快速處理。這篇文章主要給大家介紹了關(guān)于Java中消息隊(duì)列任務(wù)的平滑關(guān)閉的相關(guān)資料,需要的朋友可以參考下。2017-11-11