7個參數的用途
創建線程池一共有7個參數,從源碼可知,corePoolSize和maximumPoolSize都不能小於0,且核心線程數不能大於最大線程數。
corePoolSize
線程池核心線程數量,核心線程不會被回收,即使沒有任務執行,也會保持空閑狀態。
maximumPoolSize
池允許最大的線程數,當線程數量達到corePoolSize,且workQueue隊列塞滿任務了之后,繼續創建線程。
keepAliveTime
超過corePoolSize之后的“臨時線程”的存活時間。
unit
keepAliveTime的單位。
workQueue
當前線程數超過corePoolSize時,新的任務會處在等待狀態,並存在workQueue中,BlockingQueue是一個先進先出的阻塞式隊列實現,底層實現會涉及Java並發的AQS機制
threadFactory
創建線程的工廠類,通常我們會自定一個threadFactory設置線程的名稱,這樣就可以知道線程是由哪個工廠類創建的,可以快速定位。
handler
線程池執行拒絕策略,當線數量達到maximumPoolSize大小,並且workQueue也已經塞滿了任務的情況下,線程池會調用handler拒絕策略來處理請求。
系統默認的拒絕策略有以下幾種:
-
AbortPolicy:為線程池默認的拒絕策略,該策略直接拋異常處理。
-
DiscardPolicy:直接拋棄不處理。
-
DiscardOldestPolicy:丟棄隊列中最老的任務。
-
CallerRunsPolicy:將任務分配給當前執行execute方法線程來處理。
我們還可以自定義拒絕策略,只需要實現RejectedExecutionHandler接口即可,友好的拒絕策略實現有如下:
-
將數據保存到數據,待系統空閑時再進行處理
-
將數據用日志進行記錄,后由人工處理
ThreadPoolExecutor創建線程方式
通過下面的demo來了解ThreadPoolExecutor創建線程的過程。
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* 測試ThreadPoolExecutor對線程的執行順序
**/
public class ThreadPoolSerialTest {
public static void main(String[] args) {
//核心線程數
int corePoolSize = 3;
//最大線程數
int maximumPoolSize = 6;
//超過 corePoolSize 線程數量的線程最大空閑時間
long keepAliveTime = 2;
//以秒為時間單位
TimeUnit unit = TimeUnit.SECONDS;
//創建工作隊列,用於存放提交的等待執行任務
BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<Runnable>(2);
ThreadPoolExecutor threadPoolExecutor = null;
try {
//創建線程池
threadPoolExecutor = new ThreadPoolExecutor(corePoolSize,
maximumPoolSize,
keepAliveTime,
unit,
workQueue,
new ThreadPoolExecutor.AbortPolicy());
//循環提交任務
for (int i = 0; i < 8; i++) {
//提交任務的索引
final int index = (i + 1);
threadPoolExecutor.submit(() -> {
//線程打印輸出
System.out.println("大家好,我是線程:" + index);
try {
//模擬線程執行時間,10s
Thread.sleep(10000);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
//每個任務提交后休眠500ms再提交下一個任務,用於保證提交順序
Thread.sleep(500);
}
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
threadPoolExecutor.shutdown();
}
}
}
執行結果:
這里描述一下執行的流程:
-
首先通過 ThreadPoolExecutor 構造函數創建線程池;
-
執行 for 循環,提交 8 個任務(恰好等於maximumPoolSize[最大線程數] + capacity[隊列大小]);
-
通過 threadPoolExecutor.submit 提交 Runnable 接口實現的執行任務;
-
提交第1個任務時,由於當前線程池中正在執行的任務為 0 ,小於 3(corePoolSize 指定),所以會創建一個線程用來執行提交的任務1;
-
提交第 2, 3 個任務的時候,由於當前線程池中正在執行的任務數量小於等於 3 (corePoolSize 指定),所以會為每一個提交的任務創建一個線程來執行任務;
-
當提交第4個任務的時候,由於當前正在執行的任務數量為 3 (因為每個線程任務執行時間為10s,所以提交第4個任務的時候,前面3個線程都還在執行中),此時會將第4個任務存放到 workQueue 隊列中等待執行;
-
由於 workQueue 隊列的大小為 2 ,所以該隊列中也就只能保存 2 個等待執行的任務,所以第5個任務也會保存到任務隊列中;
-
當提交第6個任務的時候,因為當前線程池正在執行的任務數量為3,workQueue 隊列中存儲的任務數量也滿了,這時會判斷當前線程池中正在執行的任務的數量是否小於6(maximumPoolSize指定);
-
如果小於 6 ,那么就會新創建一個線程來執行提交的任務 6;
-
執行第7,8個任務的時候,也要判斷當前線程池中正在執行的任務數是否小於6(maximumPoolSize指定),如果小於6,那么也會立即新建線程來執行這些提交的任務;
-
此時,6個任務都已經提交完畢,那 workQueue 隊列中的等待 任務4 和 任務5 什么時候執行呢?
-
當任務1執行完畢后(10s后),執行任務1的線程並沒有被銷毀掉,而是獲取 workQueue 中的任務4來執行;
-
當任務2執行完畢后,執行任務2的線程也沒有被銷毀,而是獲取 workQueue 中的任務5來執行;
通過上面流程的分析,也就知道了之前案例的輸出結果的原因。其實,線程池中會線程執行完畢后,並不會被立刻銷毀,線程池中會保留 corePoolSize 數量的線程,當 workQueue 隊列中存在任務或者有新提交任務時,那么會通過線程池中已有的線程來執行任務,避免了頻繁的線程創建與銷毀,而大於 corePoolSize 小於等於 maximumPoolSize 創建的線程,則會在空閑指定時間(keepAliveTime)后進行回收。
ThreadPoolExecutor拒絕策略
在上面的測試中,我設置的執行線程總數恰好等於maximumPoolSize[最大線程數] + capacity[隊列大小],因此沒有出現需要執行拒絕策略的情況,因此在這里,我再增加一個線程,提交9個任務,來演示不同的拒絕策略。
1、AbortPolicy

2、CallerRunsPolicy
*
*
3、DiscardPolicy

4、DiscardOldestPolicy
Demo2
public class ThreadPool {
public static void main(String[] args) {
// 創建線程池 , 參數含義 :(核心線程數,最大線程數,加開線程的存活時間,時間單位,任務隊列長度)
ThreadPoolExecutor pool = new ThreadPoolExecutor(5, 8,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(2));
//設置a的值范圍在:a = (corePoolSize-1) ~ (max+queue+1) ,分析:任務數 與 活躍線程數,核心線程數,隊列長度,最大線程數的關系。
int a = 7;
for (int i = 1; i <= a; i++) {
int j = i;
pool.submit(new Runnable() {
@Override
public void run() {
//獲取線程名稱
Thread thread = Thread.currentThread();
String name = thread.getName();
//輸出
int activeCount = pool.getActiveCount();
System.out.println("任務:"+j+"-----,線程名稱:"+name+"-----活躍線程數:"+activeCount);
}
});
}
//關閉線程池
pool.shutdown();
}
}
輸出結果,觀察關系:
任務數 a = 4 , 活躍線程數4 , 任務數 < 核心線程數。
任務數 a = 5 , 活躍線程數5 , 任務數 = 核心線程數。
任務數 a = 6 , 活躍線程數5 , 任務數 < 核心線程數5 + 隊列長度2 。
任務數 a = 7 , 活躍線程數5 , 任務數 = 核心線程數5 + 隊列長度2 。
任務數 a = 8 , 活躍線程數6 , 任務數 < 最大線程數8 + 隊列長度2 . 活躍線程數是在核心線程數5的基礎上.加1個活躍線程。
任務數 a = 9 , 活躍線程數7 , 任務數 < 最大線程數8 + 隊列長度2. 活躍線程數是在核心線程數5的基礎上.加2個活躍線程。
任務數 a = 10 , 活躍線程數8 , 任務數 = 最大線程數8 + 隊列長度2. 活躍線程數是在核心線程數5的基礎上.加3個活躍線程。
任務數 a = 11 , 活躍線程數8 , 任務數 > 最大線程數8 + 隊列長度2 。拋出異常RejectedExecutionException
總結:
1、隨着任務數量的增加,會增加活躍的線程數。
2、當活躍的線程數 = 核心線程數,此時不再增加活躍線程數,而是往任務隊列里堆積。
3、當任務隊列堆滿了,隨着任務數量的增加,會在核心線程數的基礎上加開線程。
4、直到活躍線程數 = 最大線程數,就不能增加線程了。
5、如果此時任務還在增加,則: 任務數11 > 最大線程數8 + 隊列長度2 ,默認的拒絕策略會拋出異常RejectedExecutionException,拒絕任務。
