我的原則:先會用再說,內部慢慢來。
學以致用,根據場景學源碼
一、架構
1.1 UML 圖
1.2 Executors返回的線程池對象的弊端
- FixedThreadPool 和 SingleThreadPool : 允許的請求隊列長度爲 Integer.MAX_VALUE ,可能會堆積大量的請求,從而導致 OOM 。
- CachedThreadPool 和 ScheduledThreadPool : 允許的創建線程數量爲 Integer.MAX_VALUE ,可能會創建大量的線程,從而導致 OOM 。
二、 ThreadPoolExecutor 剖析
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
2.1 參數說明
參數 | 描述 |
---|---|
corePoolSize | 線程池核心線程數(平時保留的線程數) |
maximumPoolSize | 線程池最大線程數(線程池最多能起多少Worker)(當workQueue都放不下時,啓動新線程,最大線程數) |
keepAliveTime | 超出corePoolSize數量的線程的保留時間。 |
unit | keepAliveTime單位 |
workQueue | 阻塞隊列,存放來不及執行的線程 |
threadFactory | 線程工廠 |
handler | 飽和策略 |
- 關於 workQueue 阻塞隊列:
- ArrayBlockingQueue:構造函數一定要傳大小
- LinkedBlockingQueue:構造函數不傳大小會默認爲(Integer.MAX_VALUE ),當大量請求任務時,容易造成 內存耗盡。
- SynchronousQueue:同步隊列,一個沒有存儲空間的阻塞隊列 ,將任務同步交付給工作線程。
- PriorityBlockingQueue : 優先隊列
- 關於 handler 飽和策略:
- AbortPolicy(默認):直接拋棄
- CallerRunsPolicy:用調用者的線程執行任務
- DiscardOldestPolicy:拋棄隊列中最久的任務
- DiscardPolicy:拋棄當前任務
2.2 線程池規則
- 線程池數量無限制:
- 如果Worker數量 <= corePoolSize,那麼直接啓動一個核心線程來執行 Task,不會放入隊列中。
- 如果 corePoolSize < Worker數量 < maximumPoolSize:
a.並且任務隊列是LinkedBlockingDeque的時候,超過 corePoolSize 的 Task 會放在任務隊列中排隊。
b.並且任務隊列是SynchronousQueue的時候,線程池會創建新線程執行任務,這些任務也不會被放在任務隊列中。這些線程屬於非核心線程,在任務完成後,閒置時間達到了超時時間就會被清除。- 如果 maximumPoolSize < Worker數量:
a.當任務隊列是LinkedBlockingDeque,會將超過核心線程的任務放在任務隊列中排隊。也就是當任務隊列是LinkedBlockingDeque並且沒有大小限制時,線程池的 maximumPoolSize 設置是無效的,他的線程數最多不會超過 corePoolSize。
b.當任務隊列是SynchronousQueue的時候,會因爲線程池拒絕添加任務而拋出異常。
- 線程池數量有限制:
- 當LinkedBlockingDeque塞滿時,新增的任務會直接創建新線程來執行,當創建的線程數量超過最大線程數量時會拋異常。
synchronousQueue永遠沒有數量限制。因爲他根本不保持這些任務,而是直接交給線程池去執行。當任務數量超過最大線程數時會直接拋異常。
2.3 線程池處理 UML 圖
2.4 總結 ( 超級重點)
- 總共有3個地方可以放 task , core + queue + max
- max 池數量意味着同一時間有多少個線程可以併發執行,超過就排隊去。
- 到達 core 和 max 的 task 都會立刻執行,但是max內的元素過期會被回收。
- 如果 woker 數量少於 corePoolSize,那麼直接就執行 task ,如果是大於 corePoolSize,那麼就暫時塞到 queue 裏面去,等到 corePool 空閒下來,再從 queue拉取 task 來執行,如果queue沒長度限制,那麼可以一直塞到 queue裏面去排隊,如果 queue 滿了,那麼就直接讓 core 去處理,假如此時超過 maximumPoolSize 的話,那麼沒辦法,直接執行拒絕策略。
三、代碼 Demo
- 情況:
/** ===== SynchronousQueue ==== **/
//1. 每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。
//2. 每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。
//3. 每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。max 滿了,拋異常。
/** ===== LinkedBlockingDeque ==== **/
// 1. 每次+3,塞滿 core ,在 queue 裏面排隊, 由於 LinkedBlockingDeque 無限制數量,所以可以無限數量的 task 排隊,然後core空閒了就去queue取task
// 2. 每次+3,塞滿 core ,在 queue 裏面排隊,queue滿了,直接丟 max 去執行,後期回收 max 的位置
// 3. 每次+3,塞滿 core ,在 queue 裏面排隊, queue 滿裏,max 也滿了 ,拋出異常
package indi.sword.util.concurrent;
import java.util.concurrent.*;
public class _17_01_TestThreadPool {
public static void main(String[] args) throws Exception {
/** ===== SynchronousQueue ==== **/
//1. 每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。
// testQueue(6, 8, new SynchronousQueue<>());
//2. 每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。
// testQueue(3, 6, new SynchronousQueue<>());
//3. 每次+3,塞滿 core ,無法在 queue 排隊, 然後 core 也忙,但是可以丟到 max 裏面去執行。max 滿了,拋異常。
// testQueue(3, 5, new SynchronousQueue<>());// RejectedExecutionException 會導致整個 executor 停止
/** ===== LinkedBlockingDeque ==== **/
// 1. 每次+3,塞滿 core ,在 queue 裏面排隊, 由於 LinkedBlockingDeque 無限制數量,所以可以無限數量的 task 排隊,然後core空閒了就去queue取task
// testQueue(1, 3, new LinkedBlockingDeque<>());
// 2. 每次+3,塞滿 core ,在 queue 裏面排隊,queue滿了,直接丟 max 去執行,後期回收 max 的位置
// testQueue(1, 5, new LinkedBlockingDeque<>(1));
// 3. 每次+3,塞滿 core ,在 queue 裏面排隊, queue 滿裏,max 也滿了 ,拋出異常
testQueue(1, 3, new LinkedBlockingDeque<>(1));
}
/**
* 隊列任務數永遠是 0
*
* @author jeb_lin
* 5:32 PM 2019/10/24
*/
public static void testQueue(int corePoolSize,
int maximumPoolSize,
BlockingQueue<Runnable> workQueue) throws Exception {
ThreadPoolExecutor executor = new ThreadPoolExecutor(corePoolSize, maximumPoolSize, 5, TimeUnit.SECONDS, workQueue);
try {
System.out.println("輸入: corePoolSize -> " + corePoolSize + ", maximumPoolSize -> " + maximumPoolSize);
executor.execute(new MyRunnable(1));
executor.execute(new MyRunnable(2));
executor.execute(new MyRunnable(3));
System.out.println("---先開三個---");
print(executor);
executor.execute(new MyRunnable(4));
executor.execute(new MyRunnable(5));
executor.execute(new MyRunnable(6));
System.out.println("---再開三個---");
print(executor);
Thread.sleep(8000);
System.out.println("----8秒之後----");
print(executor);
} finally {
executor.shutdown();
}
}
private static void print(ThreadPoolExecutor executor) {
System.out.println("核心線程數 -> " + executor.getCorePoolSize()
+ ", 線程池數 -> " + executor.getPoolSize()
+ ", 隊列任務數 -> " + executor.getQueue().size()
+ ", queue -> " + executor.getQueue().toString());
}
private static class MyRunnable implements Runnable {
private int index;
public MyRunnable(int index){
this.index = index;
}
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("thread-" + this.index + " run");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public String toString(){
return "thread-" + index;
}
}
}
四、Executors 的 4 個常見方法底層
4.1 Executors 的四個常用方法
方法 | 描述 |
---|---|
newCachedThreadPool | 創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。 (線程可複用) |
newFixedThreadPool | 創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。 |
newScheduledThreadPool | 創建一個定長線程池,支持定時及週期性任務執行。 |
newSingleThreadExecutor | 創建一個單線程化的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定順序(FIFO, LIFO, 優先級)執行。 |
4.2 newCachedThreadPool 底層
創建一個可緩存線程池,如果線程池長度超過處理需要,可靈活回收空閒線程,若無可回收,則新建線程。 (線程可複用)
- java.util.concurrent.Executors#newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
- corePoolSize 竟然爲0 ,也就意味着過期了就全部回收,不剩餘
- maximumPoolSize 爲默認的最大值,也就是支持無限制的線程併發,也就意味着可能發生OOM
- keepAliveTime 空閒線程的最大等待時間,60s後沒有被複用立馬銷燬
- 使用 SynchronousQueue ,說明 queue 並不放東西
- corePoolSize = 0 加上 SynchronousQueue,說明 queue + core 都不放東西,那麼也就意味着只有 max 這個地方放東西,時間一到立馬回收。
4.3 newFixedThreadPool 底層
創建一個定長線程池,可控制線程最大併發數,超出的線程會在隊列中等待。
- java.util.concurrent.Executors#newFixedThreadPool(int) 方法
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- corePoolSize = n,規定了 Core 池裏面最多放 n 個線程
- maximumPoolSize = n ,規定了池子中最多就是 n 個線程 = corePoolSize,也就是全部都是核心線程,多了排隊。
- keepAliveTime = 0,因爲 corePoolSize = maximumPoolSize,也就意味着放滿 core 滿了,不會再放到 max 裏面去,那麼也意味着沒什麼好回收的了。
- LinkedBlockingQueue 鏈表內部結構,無定義長度,意味着線程可以不斷進來,不斷去排隊。允許的請求隊列長度爲 Integer.MAX_VALUE ,可能會堆積大量的請求,從而導致 OOM 。
- === 關於 LinkedBlockingQueue 與 ArrayBlockingQueue的區別 ===
4.4 newScheduledThreadPool 底層
創建一個定長線程池,支持定時及週期性任務執行。
- java.util.concurrent.ScheduledThreadPoolExecutor#ScheduledThreadPoolExecutor(int) 方法
- java.util.concurrent.Executors#newSingleThreadScheduledExecutor() 方法
public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) {
return new ScheduledThreadPoolExecutor(corePoolSize);
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue) {
this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
Executors.defaultThreadFactory(), defaultHandler);
}
- corePoolSize 自定義傳入 N ,也就是core池最多n個線程,支持n線程同時併發
- maximumPoolSize 爲 Integer.MAX_VALUE ,也就是支持無限制的線程併發,有OOM隱患,這個跟 newCachedThreadPool一樣。
- keepAliveTime = 0,意味着,執行完畢立馬回收
- 使用 DelayedWorkQueue ,意味着可以執行延遲操作。
4.5 newSingleThreadExecutor 方法
- java.util.concurrent.Executors#newSingleThreadExecutor() 方法
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- corePoolSize = maximumPoolSize = 1,意味着同一時刻只會有一個線程在執行。
- LinkedBlockingQueue 支持無限制的線程併發,有OOM隱患,這個跟 newCachedThreadPool一樣。
五、源碼剖析
5.1 execute 方法
- java.util.concurrent.ThreadPoolExecutor#execute 方法
/**
* Executes the given task sometime in the future. The task
* may execute in a new thread or in an existing pooled thread.
*
* If the task cannot be submitted for execution, either because this
* executor has been shutdown or because its capacity has been reached,
* the task is handled by the current {@code RejectedExecutionHandler}.
*
* @param command the task to execute
* @throws RejectedExecutionException at discretion of
* {@code RejectedExecutionHandler}, if the task
* cannot be accepted for execution
* @throws NullPointerException if {@code command} is null
*/
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
/*
* Proceed in 3 steps:
*
* 1. If fewer than corePoolSize threads are running, try to
* start a new thread with the given command as its first
* task. The call to addWorker atomically checks runState and
* workerCount, and so prevents false alarms that would add
* threads when it shouldn't, by returning false.
* 如果當前的線程數小於核心線程池的大小,根據現有的線程作爲第一個Worker運行的線程,
* 新建一個Worker,addWorker自動的檢查當前線程池的狀態和Worker的數量,
* 防止線程池在不能添加線程的狀態下添加線程
*
* 2. If a task can be successfully queued, then we still need
* to double-check whether we should have added a thread
* (because existing ones died since last checking) or that
* the pool shut down since entry into this method. So we
* recheck state and if necessary roll back the enqueuing if
* stopped, or start a new thread if there are none.
* 如果線程入隊成功,然後還是要進行double-check的,因爲線程池在入隊之後狀態是可能會發生變化的
*
* 3. If we cannot queue task, then we try to add a new
* thread. If it fails, we know we are shut down or saturated
* and so reject the task.
*
* 如果task不能入隊(隊列滿了),這時候嘗試增加一個新線程,如果增加失敗那麼當前的線程池狀態變化了或者線程池已經滿了
* 然後拒絕task
*/
int c = ctl.get();
//當前的Worker的數量小於核心線程池大小時,新建一個Worker。
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))//recheck防止線程池狀態的突變,如果突變,那麼將reject線程,防止workQueue中增加新線程
reject(command);
else if (workerCountOf(recheck) == 0)//上下兩個操作都有addWorker的操作,但是如果在workQueue.offer的時候Worker變爲0,
//那麼將沒有Worker執行新的task,所以增加一個Worker.
addWorker(null, false);
}
//如果workQueue滿了,那麼這時候可能還沒到線程池的maxnum,所以嘗試增加一個Worker
else if (!addWorker(command, false))
reject(command);//如果Worker數量到達上限,那麼就拒絕此線程
}
… 後續補充上!