程式設計界的國小生
- 如果下面的問題你都會的話就别在這浪費時間啦
- 1、execute全貌
- 2、拆解第一步
-
- 2.1、NPE判斷
- 2.2、擷取ctl
- 2.3、開啟線程執行任務
- 2.4、小結
- 3、拆解第二步
-
- 3.1、将任務加到任務隊列
- 3.2、recheck
- 3.3、檢查是否設定了允許核心線程逾時
- 3.4、小結
- 4、拆解第三步
-
- 4.1、開啟非核心線程處理任務
- 5、總結
author:程式設計界的國小生
date:2021/05/30
flag:不寫垃圾沒有營養的文章!
由于之前寫過線程池狀态是怎麼存儲計算的文章,是以此處不在廢話。
如不清楚線程池的狀态是怎麼玩的,是如何擷取線程池狀态和線程池目前活躍線程數的,請看下面這篇文章。
https://mp.weixin.qq.com/s/zUfMpZp4Zy3jx8oeQzt4PQ
如果下面的問題你都會的話就别在這浪費時間啦
- 從源碼層面聊聊線程池的運作流程?
- new一個線程池會開啟多少個線程?(套路!不會開啟,隻有在execute的時候才會開啟!)
- execute方法的recheck的目的是什麼?
- 如果添加任務到任務隊列後,線程池突然被其他線程給shutdown了,那麼這個剛加進去的任務會怎樣?
1、execute全貌
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);
}
2、拆解第一步
2.1、NPE判斷
if (command == null)
throw new NullPointerException();
沒啥說的,參數安全校驗。
2.2、擷取ctl
如不清楚ctl是什麼?線程池的狀态是怎麼擷取的?線程池中目前活躍線程數是多少?,請看下面這篇文章。
https://mp.weixin.qq.com/s/zUfMpZp4Zy3jx8oeQzt4PQ
2.3、開啟線程執行任務
if (workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c = ctl.get();
}
- 如果目前線程池活躍線程數小于使用者傳遞進來的核心線程數,則
addWorker();
-
的意思是:開啟一個線程去執行目前任務。源碼下篇分析。addWorker()
- 重新擷取ctl(ctrl:高3位代表線程池狀态,低29位代表目前線程池活躍線程數)
2.4、小結
// 1.如果執行任務是空,則npe。安全檢查。
if (command == null)
throw new NullPointerException();
// 2.擷取ctl,目的是下面擷取線程池狀态以及線程池目前活躍線程數來用
int c = ctl.get();
/*
* workerCountOf(c):目前線程池中活躍線程數
* corePoolSize:核心線程數
*
* 3.如果目前線程池中活躍線程數小于使用者設定的核心線程數,則開啟一個新線程來執行任務。
*/
if (workerCountOf(c) < corePoolSize) {
// 3.1 開啟一個線程來執行任務。
if (addWorker(command, true))
return;
// 因為目前線程池有變化(線程數或者線程池狀态),是以重新擷取ctl
// 注意這裡說的有變化并不僅僅是剛才開啟一個新線程,然後線程數+1導緻了ctl的變化,還有可能就是因為是多線程環境的,ctl随時都可能被其他線程給改變。
c = ctl.get();
}
3、拆解第二步
3.1、将任務加到任務隊列
能走到這裡就代表目前線程池中活躍線程數大于核心線程數了,是以:如果目前線程池中活躍線程數大于核心線程數,且線程池狀态是running狀态,則将任務offer到任務隊列裡,如果任務隊列滿了,則傳回false,也就是走【4、拆解第三步】的流程。如果添加成功,則繼續下面的recheck操作。
3.2、recheck
int recheck = ctl.get();
if (! isRunning(recheck) && remove(command))
reject(command);
為什麼要recheck線程池狀态?
因為上面offer成功後,很有可能突然被其他線程把這個線程池給shutdown了,是以這裡recheck,二次檢查線程池是否還是RUNNINT狀态,如果不是RUNNING了,則remove掉剛才offer進去的任務,并且執行拒絕政策,也就是說如果添加任務到隊列後,突然線程池被其他線程給shutdown了,則移除任務,且執行拒絕政策
3.3、檢查是否設定了允許核心線程逾時
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
這什麼神仙操作?能走到這裡的話早就代表核心線程數滿了,怎麼還判斷線程池中線程數是不是0?
因為…線程池是允許設定核心線程允許逾時的(
allowCoreThreadTimeOut
),是以
Doug Lea
老爺子在這裡再次判斷,很嚴謹,很細膩。如果沒線程了,則
addWorker(null, false);
,為什麼是null?null在
addWorker
裡有判斷用處。下文分析。
3.4、小結
// 1.如果目前線程池中活躍線程數大于核心線程數,且線程池狀态是running狀态,則将任務offer到任務隊列裡,如果任務隊列滿了,則傳回false,也就是走【4、拆解第三步】的流程。如果添加成功,則繼續下面的recheck操作。
if (isRunning(c) && workQueue.offer(command)) {
// 2. 重新擷取線程池ctl,為啥重新擷取?反複強調幾次了,因為Java是多線程的,随時可能被改變。
// 拿到後指派給個局部變量也很細膩,因為不想随時都是變化的,相當于事務一樣,我此刻拿到了,你們再變我也不管,我拿着我的快照來用
int recheck = ctl.get();
// 3. 如果添加任務到隊列後,突然線程池被其他線程給shutdown了,則移除任務,且執行拒絕政策
if (! isRunning(recheck) && remove(command))
reject(command);
// 4. 如果設定了allowCoreThreadTimeOut(允許核心線程數逾時),導緻現線上程池中活躍線程數是0,則addWorker(null, false);
else if (workerCountOf(recheck) == 0)
addWorker(null, false);
}
4、拆解第三步
4.1、開啟非核心線程處理任務
// 能走到這裡,證明如下前提:
// 1.核心線程數滿了
// 2.任務隊列滿了
// 是以走到了這裡,addWorker(command, false)的含義是開啟一個非核心線程來執行這個任務
else if (!addWorker(command, false))
// 如果非核心線程也滿了,則拒絕政策。
reject(command);
5、總結
- 如果任務是空,抛出空指針異常。(參數校驗)
- 擷取ctl,通過ctl擷取線程池中活躍線程數,看線程數是不是小于核心線程數
- 如果小于核心線程數則開啟一個核心線程去執行這個任務
- 如果目前線程池中活躍線程數大于核心線程數,則将任務添加到任務隊列
- 添加到任務隊列成功後進行recheck,主要看線程池是不是被其他線程給shutdown了,如果是的話,則remove掉剛才offer進去的任務
- 最後還是檢查核心線程數是不是都逾時了導緻線程池中活躍線程數為0,如果為0,則addWorker一個null進去。
- 如果核心線程數和任務隊列都滿了,則開啟非核心線程去執行任務
- 如果非核心線程達到了最大線程數,則拒絕政策。
【微信公衆号】