1. 程式人生 > 其它 >【併發程式設計】--- 執行緒池七大引數+四種拒絕策略 + 如何合理配置執行緒數等簡介

【併發程式設計】--- 執行緒池七大引數+四種拒絕策略 + 如何合理配置執行緒數等簡介

文章目錄
1 執行緒池七大引數
2 RejectedExecutionHandler--- 四種拒絕策略(官方提供)
3 threadFactory --- 執行緒工廠相關的注意事項
4 如何自己new一個執行緒池 --- 簡單結合了一下我們的專案
5 實際工作中不允許使用Executors建立執行緒池的原因
6 如何合理配置最大執行緒數
1 執行緒池七大引數
其實執行緒池的七大引數,我算一直都是比較熟悉的,因為我們現在的好幾個專案裡,有好幾處執行緒池都是我配置的,而且大多都至少在線上經歷了1年多的洗禮了。。。 其實很早之前我就想整理一下這一塊的內容的,但是卻一直沒有付諸於行動!!!

恰巧,最近發現了嗶哩嗶哩這片知識的藍海,又有幸看到了尚矽谷周陽老師的公開課,感覺講的挺好的,本篇文章大多是在此基礎上的總結。

執行緒池七大引數總結如下:


進一步解釋如下:

1.在建立了執行緒池後,等待提交過來的任務請求。
2.當呼叫execute()方法新增一個請求任務時,執行緒池會做如下判斷:
2.1 如果正在執行的執行緒數量小於corePoolSize,那麼馬上建立執行緒執行這個任務;
2.2 如果正在執行的執行緒數量大於或等於corePoolSize,那麼將這個任務放入佇列;
2.3 如果這時候佇列滿了且正在執行的執行緒數量還小於maximumPoolSize,那麼還是要建立非核心執行緒立刻執行這個任務;
2.4 如果佇列滿了且正在執行的執行緒數量大於或等於maximumPpolSize,那麼執行緒池會啟動飽和拒絕策略來執行。
3.當一個執行緒完成任務時,它會從佇列中取下一個任務來執行。
4.當一個執行緒無事可做超過一定的時間(keepAliveTime) 時,執行緒池會判斷:
如果當前執行的執行緒數大於corePoolSize,那麼這個執行緒就被停掉。
所以執行緒池的所有任務完成後它最終會收縮到corePoolSize的大小。
執行緒池工作的主要流程圖如下:


最大可同時存在於執行緒池中的任務: 最大執行緒數 + 阻塞佇列的長度。 —》這應該很好理解吧。。。

2 RejectedExecutionHandler— 四種拒絕策略(官方提供)
當阻塞佇列滿了,且沒有空閒的工作執行緒,如果繼續提交任務,必須採取一種策略處理該任務,執行緒池提供了4種策略:
(1)AbortPolicy:直接丟擲異常,預設策略;
(2)CallerRunsPolicy:用呼叫者所在的執行緒來執行任務;
(3)DiscardOldestPolicy:丟棄阻塞佇列中靠最前的任務,並執行當前任務;
(4)DiscardPolicy:直接丟棄任務;
當然也可以根據應用場景實現RejectedExecutionHandler介面,自定義飽和策略(拒絕策略),如記錄日誌或持久化儲存不能處理的任務 —》 我在工作中其實就用到了自定義拒絕策略。

有興趣的可以用程式碼進行測試一下,挺簡單的。

3 threadFactory — 執行緒工廠相關的注意事項
Executors工具類裡給提供了一個預設的執行緒工廠 —> defaultThreadFactory,其對執行緒的命名規則為: “pool-數字-thread-數字”。

但是實際開發中我們常常都不會使用這個預設的 —> 我在專案中就沒用。

因為我們完全可以按照自己的業務給每個新建的執行緒設定一個具有識別度的執行緒名,這樣的話,如果真出現了問題,那肯定就會比較容易問題排查。

當然還可以更加自由的對執行緒做更多的設定,比如設定所有的執行緒為守護執行緒。我看阿里的《Java開發手冊》上甚至都將其作為了【強制】性規約:


4 如何自己new一個執行緒池 — 簡單結合了一下我們的專案
直接上程式碼了,有興趣的可以自己測試一下:

package com.nrsc.ch6.tp;

import lombok.extern.slf4j.Slf4j;
import org.junit.Test;

import java.util.concurrent.*;
import java.util.concurrent.atomic.AtomicInteger;

@Slf4j
public class ThreadPoolDemo {
/***
* 自定義拒絕策略
*/
RejectedExecutionHandler selfRejectedHandler = (r, pool) -> {
//我們的專案,如果將log的級別配置為warn或error級別就會發郵件+微信進行通知
//其實執行緒池的阻塞佇列一般都會配的相對比較大,所以基本不會讓任務被拒絕掉 -- 自己的經驗
log.warn("方法{}被執行緒池{}拒絕了,請做及時補償處理", r.toString(), pool);
};


//定義執行緒組名稱,在jstack問題排查時,非常有幫助
private final String namePrefix = "Contract==";
private final AtomicInteger nextId = new AtomicInteger(1);

/***
* 自定義執行緒工廠
*/
ThreadFactory threadFactory = (r) -> {
Thread thread = new Thread(r);
thread.setName(namePrefix + nextId.getAndIncrement());
return thread;
};

/***
* 使用自定義的執行緒工廠 和 拒絕策略
* 實際生產中變數一般寫在配置檔案裡
*/
ThreadPoolExecutor threadPoolExecutor1 = new ThreadPoolExecutor(
3, 5, 3, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
threadFactory,
selfRejectedHandler);


/***
* 使用JDK提供的執行緒工廠 和 拒絕策略
*/
ThreadPoolExecutor threadPoolExecutor2 = new ThreadPoolExecutor(
3, 5, 3, TimeUnit.SECONDS,
new LinkedBlockingDeque<>(3),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());


/***
* 自定義的執行緒工廠 和 拒絕策略 測試
*/
@Test
public void test1() {
for (int i = 1; i <= 10; i++) {
threadPoolExecutor1.execute(() -> {
log.info("當前執行緒為{}", Thread.currentThread().getName());
});
}
}

/***
* 使用JDK提供的執行緒工廠 和 拒絕策略
*/
@Test
public void test2() {
for (int i = 1; i <= 10; i++) {
threadPoolExecutor2.execute(() -> {
log.info("當前執行緒為{}", Thread.currentThread().getName());
});
}
}
}

5 實際工作中不允許使用Executors建立執行緒池的原因
靜態工廠裡預設的threadFactory,執行緒的命名規則是“pool-數字-thread-數字”。
相信很多人在學校裡上學時,老師一般都會教你用Executors工具類來建立執行緒池,當然平常我寫部落格或者做一些小demo時,也喜歡這麼用。

但是隻要有過工作經驗的,相信肯定都知道,在實際的生產中我們絕對不會這樣用。其實原因很簡單,如果你懂了阻塞佇列的話,再簡單摟一眼利用Executors工具類建立執行緒池的原始碼,你就會發現具體的原因了。

Executors建立執行緒池的幾個常用方法的原始碼:


Integer.MAX_VALUE = 2147483647 —> 21億多。。。。

這裡用阿里《JAVA開發手冊》中的【強制】性規約來做出工作中不能用Executors建立執行緒池的具體解釋:


6 如何合理配置最大執行緒數
(1)CPU密集型:

定義:CPU密集型的意思就是該任務需要大量運算,而沒有阻塞,CPU一直全速執行。
CPU密集型任務只有在真正的多核CPU上才可能得到加速(通過多執行緒)。
CPU密集型任務配置儘可能少的執行緒數。

CPU密集型執行緒數配置公式:CPU核數+1個執行緒的執行緒池

(2)IO密集型:

定義:IO密集型,即該任務需要大量的IO,即大量的阻塞。
在單執行緒上執行IO密集型任務會導致浪費大量的CPU運算能力浪費在等待。
所以IO密集型任務中使用多執行緒可以大大的加速程式執行,即使在單核CPU上,這種加速主要利用了被浪費掉的阻塞時間。

第一種配置方式:
由於IO密集型任務執行緒並不是一直在執行任務,則應配置儘可能多的執行緒。
配置公式:CPU核數 * 2。

第二種配置方式:
IO密集型時,大部分執行緒都阻塞,故需要多配置執行緒數。
配置公式:CPU核數 / 1 – 阻塞係數(0.8~0.9之間)
比如:8核 / (1 – 0.9) = 80個執行緒數