執行緒池7個引數拿捏死死的,完爆面試官
執行緒池
- 上一章節我們介紹的四種建立執行緒的方式算是熱身運動了。執行緒池才是我們的重點介紹物件。
- 這個是JDK對執行緒池的介紹。
- 但是你會問為什麼上面我們建立執行緒池的方式是通過
Executors.newCachedThreadPool()
;
-
關於Exectors內部提供了很多快捷建立執行緒的方式。這些方法內部都是依賴
ThreadPoolExecutor
。所以執行緒池的學習就是ThreadPoolExecutor
。 -
執行緒池
ThreadPoolExecutor
正常情況下最好用執行緒工廠來建立執行緒。他的作用是用來處理每一次提交過來的任務;ThreadPoolExecutor
可以解決兩個問題- 在很大併發量的情況下執行緒池不僅可以提供穩定的處理還可以減少執行緒之間的排程開銷。
- 並且執行緒池提供了對執行緒和資源的回收及管理。
-
另外在內部
ThreadPoolExecutor
提供了很多引數及可擴充套件的地方。同時他也內建了很多工廠執行器方法供我們快速使用,比如說Executors.newCacheThreadPool()
:無限制處理任務。 還有Executors.newFixedThreadPool()
:固定執行緒數量;這些內建的執行緒工廠基本上能滿足我們日常的需求。如果內建的不滿足我們還可以針對內部的屬性進行個性化設定
- 通過跟蹤原始碼我們不難發現,內建的執行緒池構建都是基於上面提到的7個引數進行設定的。下面我畫了一張圖來解釋這7個引數的作用。
- 上面這張圖可以解釋
corePoolSize
、maxiumPoolSize
、keepAliveTime
、TimeUnit
、workQueue
這五個引數。關於threadFactory
、handler
是形容過程中的兩個引數。 - 關於
ThreadPoolExecutor
我們還得知道他雖然是執行緒池但是也並不是一開始就初始化好執行緒的。而是根據任務實際需求中不斷的構建符合自身的執行緒池。那麼構建執行緒依賴誰呢?上面也提到了官方推薦使用執行緒工廠。就是我們這裡的ThreadFactory
類。 - 比如
Executors.newFixedThreadPool
是設定了固定的執行緒數量。那麼當任務超過執行緒數和佇列長度總和時,該怎麼辦?如果真的發生那種情況我們只能拒絕提供執行緒給任務使用。那麼該如何拒絕這裡就涉及到我們的RejectExecutionHandler
- 點進原始碼我們可以看到預設的佇列好像是
LinkedBlockingQueue
; 這個佇列是連結串列結構的怎麼會有長度呢? 的確是但是Executors
還給我們提供了很多擴充套件性。如果我們自定義的話我們能夠發現還有其他的
核心數與匯流排程數
- 這裡對應
corePoolSize
和maxiumPoolSize
。
final ThreadPoolExecutor executorService = (ThreadPoolExecutor) Executors.newFixedThreadPool(10);
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("我是執行緒1做的事情");
}
});
- 我們已
newFixedThreadPool
來分析下。首先它需要一個整數型引數。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 而實際上內部是構建一個最大執行緒數量為10,且10個執行緒都是核心執行緒(公司核心員工);這10個執行緒是不會有過期時間一說的。過期時間針對非核心執行緒存活時間(公司外包員工)。
- 當我們執行
execute
方法時。點進去看看我們發現
- 首先會判斷當前任務數是否超過核心執行緒數,如果沒有超過則會新增值核心執行緒佇列中。注意這裡並沒有去獲取是否有空閒執行緒。而是隻要滿足小於核心執行緒數,進來的任務都會優先分配執行緒。
- 但是當任務數處於(corePoolSize,maxiumPoolSize】之間時,執行緒池並沒有立馬建立非核心執行緒,這點我們從原始碼中可以求證。
- 這段程式碼時上面if 判斷小於核心執行緒之後的if , 也就是如果任務數大於核心執行緒數。優先執行該if 分支。意思就是會將核心執行緒來不及處理的放在佇列中,等待核心執行緒緩過來執行。像我們上面所說如果這個時候我們用的時有邊界的佇列的話,那麼佇列總有放滿的時候。這個時候執行來到我們第三個if分支
- 這裡還是先將任務新增到非核心佇列中。false表示非核心。如果能新增進去說明還沒有溢位非核心數。如果溢位了正好if新增就是false . 就會執行了拒絕策略。
- 下面時executor執行原始碼
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);
}
思考
- 基於上面我們對核心數和總數的講述,我們來看看下面這段程式碼是否能夠正確執行吧。
public static void main(String[] args) throws InterruptedException {
ThreadPoolExecutor executorService = new ThreadPoolExecutor(10,20,0,TimeUnit.SECONDS,new ArrayBlockingQueue<>(10));
for (int i = 0; i < 100; i++) {
int finalI = i;
executorService.execute(new Runnable() {
@SneakyThrows
@Override
public void run() {
System.out.println(finalI);
TimeUnit.SECONDS.sleep(1000);
}
});
}
}
- 很不幸,我們的執行報錯了。而且出發了
ThreadPoolExecutor
中的拒絕策略。而且分析日誌我們能夠發現成功執行的有20個任務。分別是【0,9】+【20,29】這20個任務。 - 拒絕我們很容易理解。因為我們設定了最大20個執行緒數加上長度為10的佇列。所以該執行緒城同時最多隻能支援30個任務的併發。另外因為我們每一個任務執行時間至少在1000秒以上,所以程式執行到第31個的時候其他都沒有釋放執行緒。沒有空閒的執行緒給第31個任務所以直接拒絕了。
- 那麼為什麼是是【0,9】+【20,29】呢?上面原始碼分析我們也提到了,進來的任務優先分配核心執行緒數,然後是快取到佇列中。當佇列滿了之後才會分配非核心數。當第31個來臨直接出發拒絕策略,所以不管是核心執行緒還是非核心執行緒都沒有時間處理佇列中的10個執行緒。所以列印是跳著的。
- 避免回表,引入索引下推|提高索引命中率 | 提前下班啦
- TDengine 時序性資料庫為什麼海量資料下不卡頓呢
- 神奇的XPath,快速完成前端及XML的元素定位,茫茫大海不迷路
- springboot通用分支處理---還在硬編碼特殊處理邏輯?超級管理員不應該被區別對待
- Spring事務太強大了,相容資料庫同時給我們提供多種組合應對業務需求
- java物件在記憶體中如何分佈 | java上鎖原來就是記憶體佔位,so easy
- linux三劍客之編輯器sed出廠
- linux三劍客awk教你如何裁剪結果集
- 執行緒池7個引數拿捏死死的,完爆面試官
- 執行緒池存在的意義
- 多年程式設計師總結下來的懶人必備指令碼之進度條⚠️製作
- java中的static關鍵字說清楚還得靠JVM
- 設計模式存在哪些關聯關係,六種關係傻傻分不清--- UML圖示詳解
- 每次需求評審產品總是讓我提高程式碼複用,說白了就是合成複用原則
- 越級上報不可行,各司其職才是王道---迪米特法則
- 偏向鎖/輕量鎖/重級鎖鎖鎖更健康,上鎖解鎖到底是怎麼完成實現的,我來告訴你
- 狸貓換太子里氏替換原則;不要一味的進行抽象否則最後你無法hold你的物件
- 設計模式是我擺脫碼畜的唯一出路---依賴倒轉原則
- 學好數理化,寫遍所有程式碼都不怕,我用數學分類討論的思想解決
- synchronized已經不在臃腫了,放下對他的成見之初識輕量級鎖