時隔上一篇技術文章更新差不多有3個星期了窗宇,原因的話在上一篇文章中寫啦。廢話不多說瞳浦,開始我們的線程池源碼的第二輪閱讀担映。
回顧
簡單回顧下上一篇線程池源碼中涉及的兩個方法废士,一個是execute()
執(zhí)行任務的入口叫潦,還有一個是addWorker()
最通俗地理解就是是否需要添加新線程。而在addWoker()
的末尾有這樣一段代碼
if (workerAdded) {
t.start();
workerStarted = true;
}
明顯地看到這里通過start()
方法開啟了多線程官硝,而如果想要看線程的執(zhí)行邏輯矗蕊,就需要去到對應類中查看run方法短蜕,這里的t就是Worker
類里面的一個成員變量,所以重點要看Worker
類中的run()
方法傻咖。
runWorker()
run()
方法的源碼如圖所示朋魔,最后是到了runWorker()
直接來看runWorker的源碼
- 開始是一個循環(huán),要么執(zhí)行worker自帶的第一個任務(firstTask)卿操,要么通過
getTask()
獲取任務 - 有任務首先得保證線程池是正常的警检,以下兩種情況均調用
wt.interrupt()
給線程設置中斷標志位- 線程池處于STOP狀態(tài),也就是不接受新任務害淤,也不執(zhí)行隊列中的任務
- 如果線程的標志位已經為true扇雕,那么清楚標志位,此時的線程池狀態(tài)為STOP狀態(tài)窥摄,這里看起來可能比較別扭镶奉,有了第一種情況為什么還要第二種,不理解的可以先略過崭放,后面會講的哨苛。
- 正常情況下是調用
beforeExecute()
和afterExecute()
包裹者task.run()
看一下是如何自定義前置和后置執(zhí)行邏輯
由于是換電腦寫了,所以例子可能和前一篇文章的不完全一樣币砂,但是表達的是同一個意思
public class ThreadPoolExamples {
public static void main(String[] args) {
ThreadPoolExecutor executor = new MyThreadPoolExecutor(5, 5, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<>());
MyThread myThread = new MyThread();
executor.execute(myThread);
}
}
class MyThread extends Thread {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " is running");
}
}
class MyThreadPoolExecutor extends ThreadPoolExecutor {
@Override
protected void beforeExecute(Thread t, Runnable r) {
System.out.println("【" + Thread.currentThread().getName() + " custom before execute】");
}
@Override
protected void afterExecute(Runnable r, Throwable t) {
System.out.println("【" + Thread.currentThread().getName() + " is done】");
}
MyThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue) {
super(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue);
}
}
首先就是要創(chuàng)建自己的MyThreadPoolExecutor
類建峭,繼承ThreadPoolExecutor
,然后重寫beforeExecute()
和afterExecute()
定義自己的邏輯即可道伟,看下測試結果
可以看到前置和后置都已經按照既定的邏輯在運行了迹缀,有趣的是,22分鐘過去了(不要問我為什么這么久蜜徽,拿外賣吃東西去了)線程池還是沒有停祝懂,為什么會這樣呢。
注意前面分析runWorker()
第一步的時候是一個循環(huán)拘鞋,然后通過firstTask 或者getTask()
獲取任務砚蓬,如果兩種方式都獲取不到任務,線程池就應該退出盆色,看來奧秘在getTask()
中灰蛙。
getTask()
主要目的顧名思義就是獲取到需要執(zhí)行的任務,直接看源碼
- 一進來也是一個死循環(huán)隔躲,可以先聚焦什么時候會退出循環(huán)摩梧,肯定是不正常的情況下會退出
- 當線程池狀態(tài)不處于RUNNING或者SHUTDOWN的時候,或者是當線程處于SHUTDOWN但是工作隊列中沒有任務
- 當wc大于最大線程數并且工作隊列為空的時候宣旱,或者當wc大于核心線程數并且timedOut為true并且核心隊列為空的時候仅父,或者如果設置了allowCoreThreadTimeOut,并且wc > 1或者核心隊列為空的時候
- 除了不正常的情況,接下來就是從工作隊列中獲取任務笙纤,不過是根據timed的來決定是用
poll()
還是take()
耗溜。 - 如果能取出任務,就直接返回任務省容;如果沒有任務抖拴,要么超時設置timedOut為ture,要么是拋出異常重置timedOut為false腥椒。
可以看到阿宅,只有上述不正常的情況下退出循環(huán),任務返回null笼蛛,進而導致
runWorker()
中的while循環(huán)退出家夺,最后整個線程池關閉。否則都是會一直在getTask()
這里死循環(huán)伐弹。
到這里拉馋,為什么說線程池能夠節(jié)省資源呢,是因為其實它創(chuàng)建的線程的消耗只是體現在了Worker類的創(chuàng)建中惨好,把其它要完成的任務放在工作隊列里面煌茴,然后getTask()
獲取任務,最后執(zhí)行任務(調用task.run()
)
拒絕策略
這個是在最開始execute()
的時候調用的
詳細是如何請看下面的動圖
可以看到日川,最后是調用了RejectedExecutionHandler
接口中的rejectedExecution(Runnable r, ThreadPoolExecutor executor);
方法蔓腐,然后默認是有4種實現方式
有開放的接口,那肯定是能自定義實現類的
class MyRejectedExecutionHandler implements RejectedExecutionHandler {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor executor) {
System.out.println("task is rejected");
}
}
該說的方法也基本都說了龄句,用到的工作隊列(BlockingQueue)在后面會另說回论,下一篇文章就總結下jdk的線程池啦!
創(chuàng)作不易分歇,如果對你有幫助傀蓉,歡迎點贊,收藏和分享啦职抡!