摘要描述:【強制】線程池不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式,這樣 的處理方式讓寫的同學更加明確線程池的運行規則,規避資源耗盡的風險。 說明:Executors 返回的線程池對象的弊端如下: 1)FixedThreadPool 和 SingleThreadPool: 允許的請求隊列長度爲 Integer.MAX_VALUE,可能會堆積大量的請求,從而導致 OOM。 2)CachedThreadPool 和 ScheduledThreadPool: 允許的創建線程數量爲 Integer.MAX_VALUE,可能會創建大量的線程,從而導致 OOM。
優勢:
(1)降低資源消耗。通過重複利用已創建的線程降低線程創建、銷燬線程造成的消耗。
(2)提高響應速度。當任務到達時,任務可以不需要等到線程創建就能立即執行。
(3)提高線程的可管理性。線程是稀缺資源,如果無限制的創建,不僅會消耗系統資源,還會降低系統的穩定性,使用線程池可以進行統一的分配、調優和監控。
一、線程池的原理
1.1 線程池剛創建時,裏面沒有一個線程。任務隊列是作爲參數傳進來的。不過,就算隊列裏面有任務,線程池也不會馬上執行它們。
1.2. 當調用execute()方法添加一個任務時,任務通過 execute(Runnable)方法被添加到線程池,任務就是一個 Runnable類型的對象,任務的執行方法就是 Runnable類型對象的run()方法。 當一個任務通過execute(Runnable)方法欲添加到線程池時,線程池會做如下判斷:
a. 如果正在運行的線程數小於corePoolSize,即使線程池中的線程都處於空閒狀態,也會創建新的線程運行這個任務。
b. 如果正在運行的線程數大於或者等於corePoolSize,但是緩衝隊列 workQueue未滿,那麼任務將被放入緩存隊列。
c. 如果正在運行的線程數大於corePoolSize,緩衝隊列workQueue已滿,而且正在運行的線程數量小於maximumPoolSize,那麼還是要創建新的線程運行這個任務。
d. 如果正在運行的線程數大於corePoolSize,緩衝隊列workQueue已滿,而且正在運行的線程數量大於或等於maximumPoolSize,那麼線程池會拋出異常,告訴調用者“我不能再接受任務了”。1.3 當一個線程完成任務時,它會從隊列中取下一個任務來執行。
1.4 當一個線程無事可做,超過一定的時間(keepAliveTime)時,線程池會判斷,如果當前運行的線程數大於corePoolSize時,那麼這個線程會被終止,線程池可以動態的調整池中的線程數,當線程池的所有任務完成後,它最終會收縮到corePoolSize的大小。
1.5 處理任務的優先級爲: 核心線程corePoolSize、任務隊列workQueue、最大線程maximumPoolSize,如果三者都滿了,使用handler處理被拒絕的任務。
二、線程池的使用場景
(1)單個任務處理的時間比較短;
(2)需要處理的任務數量大;
三、線程池的配置參數
new ThreadPoolExecutor(int corePoolSize, int maximumPoolSize,long keepAliveTime, TimeUnit unit,BlockingQueue workQueue,RejectedExecutionHandler handler)
(1)corePoolSize: 線程池維護線程的最少數量 (core : 核心)
(2)maximumPoolSize: 線程池維護線程的最大數量 ,如果當線程池中的運行的線程數量到達這個數字時,新來的任務會拋出異常。
(3)keepAliveTime: 線程池維護線程所允許的空閒時間 ,表示線程沒有任務執行時最多能保持多少時間會停止,然後線程池維護的線程數目維持在corePoolSize。
(4)unit: 線程池維護線程所允許的空閒時間的單位 。
(5)workQueue: 線程池所使用的阻塞隊列,用來緩存等待執行的任務,如果當前對線程的需求超過了corePoolSize大小,纔會放在這裏。
(6)handler: 如果執行線程已滿,線程池對拒絕任務的處理策略。線程池的阻塞隊列包含哪幾種選擇?
ArrayBlockingQueue:一個有邊界的阻塞隊列,它的內部實現是一個數組。它的容量在初始化時就確定不變。
LinkedBlockingQueue:阻塞隊列大小的配置是可選的,其內部實現是一個鏈表。
PriorityBlockingQueue:是一個沒有邊界的隊列,所有插入到PriorityBlockingQueue的對象必須實現java.lang.Comparable接口,隊列優先級的排序就是按照我們對這個接口的實現來定義的。
SynchronousQueue:隊列內部僅允許容納一個元素。當一個線程插入一個元素後會被阻塞,除非這個元素被另一個線程消費。線程池對拒絕任務的處理策略有哪幾種?
ThreadPoolExecutor.AbortPolicy():拋出java.util.concurrent.RejectedExecutionException異常 。
ThreadPoolExecutor.CallerRunsPolicy(): 重試添加當前的任務,他會自動重複調用execute()方法 。
ThreadPoolExecutor.DiscardOldestPolicy(): 拋棄舊的任務 。
ThreadPoolExecutor.DiscardPolicy(): 拋棄當前的任務。
四、線程池的使用
package com.layduo.web.test;
import java.io.Serializable;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @author layduo
* @createTime 2019年11月19日 上午10:42:39
*
* @resource by https://www.cnblogs.com/sunhaoyu/articles/6955923.html
*/
public class MyThreadPoolExecutor {
private static int produceTaskSleepTime = 1000;
private static int consumeTaskSleepTime = 5000;
private static int produceTaskMaxNumber = 10; // 定義最大添10個線程到線程池中
private static int corePoolSize = 3;
private static int maximumPoolSize = 5;
private static long keepAliveTime = 3;
public static void main(String[] args) throws InterruptedException {
// 創建線程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, keepAliveTime,
TimeUnit.SECONDS, new ArrayBlockingQueue<>(3), new ThreadPoolExecutor.CallerRunsPolicy());
for (int i = 1; i <= produceTaskMaxNumber; i++) {
try {
// 一個任務,並將其加入到線程池
System.out.println("===================================================");
String work = "task@ " + i;
System.out.println("execute : " + work);
threadPoolExecutor.execute(new ThreadPoolTask(work, threadPoolExecutor));
// 便於觀察,等待一段時間
Thread.sleep(produceTaskSleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
boolean flag = true;
while (flag) {
if (threadPoolExecutor.getQueue().size() <= 0 && threadPoolExecutor.getActiveCount() <= 0) {
flag = false;
System.out.println();
System.out.println("====>線程池線程任務執行完畢");
printlnThread(threadPoolExecutor);
// 關閉線程池
threadPoolExecutor.shutdown();
// 檢測沒有關閉線程池,則強制關閉
if (threadPoolExecutor.isTerminated()) {
threadPoolExecutor.shutdownNow();
System.out.println("========強制關閉線程池========");
}
}
Thread.sleep(3000);
}
}
// 線程池執行的任務
public static class ThreadPoolTask implements Runnable, Serializable {
private static final long serialVersionUID = 0;
// 保存任務所需要的數據
private Object threadPoolTaskData;
private ThreadPoolExecutor threadPool;
public ThreadPoolTask(Object works, ThreadPoolExecutor threadPoolExecutor) {
this.threadPoolTaskData = works;
this.threadPool = threadPoolExecutor;
}
@Override
public void run() {
printlnThread(threadPool);
System.out.println();
System.out.println("run ------> " + threadPoolTaskData + " by " + Thread.currentThread().getName());
System.out.println("===================================================");
try {
// 便於觀察,等待一段時間
Thread.sleep(consumeTaskSleepTime);
} catch (InterruptedException e) {
e.printStackTrace();
}
threadPoolTaskData = null;
}
public Object getTask() {
return this.threadPoolTaskData;
}
}
public static void printlnThread(ThreadPoolExecutor threadPool) {
System.out.println();
int queueSize = threadPool.getQueue().size();
System.out.println("====>當前排隊任務數:" + queueSize);
int activeCount = threadPool.getActiveCount();
System.out.println("====>當前活動任務數:" + activeCount);
long completedTaskCount = threadPool.getCompletedTaskCount();
System.out.println("====>執行完成任務數:" + completedTaskCount);
long taskCount = threadPool.getTaskCount();
System.out.println("====>線程池執行任務數:" + taskCount);
// 通過這個數據可以知道線程池是否曾經滿過。如該數值等於線程池的最大大小,則表示線程池曾經滿過。
int largest = threadPool.getLargestPoolSize();
System.out.println("====>創建最大線程數:" + largest);
System.out.println("====>線程池創建線程是否已滿:" + (largest == maximumPoolSize ? "是" : "否"));
long poolSize = threadPool.getPoolSize();
System.out.println("====>總線程數:" + poolSize);
}
}
運行輸出控制檯:
===================================================
execute : task@ 1
====>當前排隊任務數:0
====>當前活動任務數:1
====>執行完成任務數:0
====>線程池執行任務數:1
====>創建最大線程數:1
====>線程池創建線程是否已滿:否
====>總線程數:1
run ------> task@ 1 by pool-1-thread-1
===================================================
===================================================
execute : task@ 2
====>當前排隊任務數:0
====>當前活動任務數:2
====>執行完成任務數:0
====>線程池執行任務數:2
====>創建最大線程數:2
====>線程池創建線程是否已滿:否
====>總線程數:2
run ------> task@ 2 by pool-1-thread-2
===================================================
===================================================
execute : task@ 3
====>當前排隊任務數:0
====>當前活動任務數:3
====>執行完成任務數:0
====>線程池執行任務數:3
====>創建最大線程數:3
====>線程池創建線程是否已滿:否
====>總線程數:3
run ------> task@ 3 by pool-1-thread-3
===================================================
===================================================
execute : task@ 4
===================================================
execute : task@ 5
====>當前排隊任務數:1
====>當前活動任務數:3
====>執行完成任務數:1
====>線程池執行任務數:5
====>創建最大線程數:3
====>線程池創建線程是否已滿:否
====>總線程數:3
run ------> task@ 4 by pool-1-thread-1
===================================================
===================================================
execute : task@ 6
====>當前排隊任務數:1
====>當前活動任務數:3
====>執行完成任務數:2
====>線程池執行任務數:6
====>創建最大線程數:3
====>線程池創建線程是否已滿:否
====>總線程數:3
run ------> task@ 5 by pool-1-thread-2
===================================================
===================================================
execute : task@ 7
====>當前排隊任務數:1
====>當前活動任務數:3
====>執行完成任務數:3
====>線程池執行任務數:7
====>創建最大線程數:3
====>線程池創建線程是否已滿:否
====>總線程數:3
run ------> task@ 6 by pool-1-thread-3
===================================================
===================================================
execute : task@ 8
===================================================
execute : task@ 9
===================================================
execute : task@ 10
====>當前排隊任務數:3
====>當前活動任務數:4
====>執行完成任務數:3
====>線程池執行任務數:10
====>創建最大線程數:4
====>線程池創建線程是否已滿:否
====>總線程數:4
run ------> task@ 10 by pool-1-thread-4
===================================================
====>當前排隊任務數:2
====>當前活動任務數:4
====>執行完成任務數:4
====>線程池執行任務數:10
====>創建最大線程數:4
====>線程池創建線程是否已滿:否
====>總線程數:4
run ------> task@ 7 by pool-1-thread-1
===================================================
====>當前排隊任務數:1
====>當前活動任務數:4
====>執行完成任務數:5
====>線程池執行任務數:10
====>創建最大線程數:4
====>線程池創建線程是否已滿:否
====>總線程數:4
run ------> task@ 8 by pool-1-thread-2
===================================================
====>當前排隊任務數:0
====>當前活動任務數:4
====>執行完成任務數:6
====>線程池執行任務數:10
====>創建最大線程數:4
====>線程池創建線程是否已滿:否
====>總線程數:4
run ------> task@ 9 by pool-1-thread-3
===================================================
====>線程池線程任務執行完畢
====>當前排隊任務數:0
====>當前活動任務數:0
====>執行完成任務數:10
====>線程池執行任務數:10
====>創建最大線程數:4
====>線程池創建線程是否已滿:否
====>總線程數:3