文章目錄
線程池的使用原理分析
一、包含的知識點
- 爲什麼需要線程池
- 線程池API
- 線程池創建方式
- 線程池創建方式
- 線程池ThreadPoolExecutor
- 線程池原理分析
- Callable/Future原理分析
二、爲什麼需要線程池
線程是任務執行的基本單位, 但是計算機能夠執行的線程數是有一定限制的, 創建過多的線程會引起系統資源不足, 原因如下:
- 線程的創建、銷燬需要消耗過多的系統資源
- 線程上下文切換頻繁, 造成系統資源嚴重消耗
爲了解決上面的問題, 提出了**線程池**概念, 那麼線程池具有哪些優點呢 ?
- 降低創建線程、銷燬線程帶來的性能開銷
- 合理的線程數,一定程度的提高了響應速度, 當有新任務需要執行時, 不需要等待線程創建可以立即執行
- 合理的設置線程池大小, 避免線程數過多造成系統資源問題
三、線程池API
3.1 創建線程的方式
在前面關於併發的文章中,有提到創建線程的方式, 這裏再提一下
-
繼承Thread
複寫run方法, 沒有返回值
-
實現Runnable
複寫run方法, 沒有返回值, 可以沒有run方法之外的其它方法
-
實現Callbale、Future
複寫call方法(類似run方法), 帶有返回值,
-
線程池
提供線程隊列, 隊列中包含所有等待狀態的線程, 減少了創建、銷燬開銷, 提高了響應速度
這裏我們將講解使用線程池創建線程的方式
3.2 創建線程池的方式
爲了方便對線程池的使用, Executors提供了創建線程池的工廠方法, 包含:
- newSingleThreadExecutor, 創建線程數爲1的線程池, 若存在空閒線程則執行, 若不存在空閒線程, 則暫緩在任務隊列中
- newFixedThreadPool,創建線程數爲固定大小的線程池, 任務提交時如果有空閒線程則立即執行,如果沒有空閒線程, 暫緩在任務隊列中, 等待空閒的線程再繼續執行。
- newCachedThreadPool,根據實際情況調整線程數量的線程池, 最大線程池個數爲Integer.MAX_VALUE, 任務提交時, 如果有空閒的線程則執行任務, 如果無任務執行則不創建線程, 並且線程再空閒60s之後會自動回收**(可能引起創建線程過多的問題)**
- newScheduledThreadPool, 創建指定線程數量的線程池, 其除了具有其它線程功能, 還帶有延遲、週期性執行任務的功能, 類似定時器。
3.3 線程統一封裝類型ThreadPoolExecutor
ThreadPoolExecutor有多個構造函數, 這裏我們看下最全參數的構造函數, 其它構造函數都會派生成這個構造函數
public ThreadPoolExecutor(int corePoolSize, // 核心線程數量
int maximumPoolSize, // 最大線程數量
long keepAliveTime, // 超時時間, 超出核心線程數量之外的線程空閒存活時間
TimeUnit unit, // 時間單位
BlockingQueue<Runnable> workQueue, // 保存執行任務的隊列
ThreadFactory threadFactory, // 創建新線程使用的工廠
RejectedExecutionHandler handler) { // 拒絕策略, 任務無法執行時處理方式
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
從構造函數看出, ThreadPoolExecutor創建時沒有創建線程, 當有任務需要執行時纔會創建線程(線程的初始化和普通線程初始化方式一樣), 在完成任務後線程不會銷燬而是以掛起的狀態返回線程池, 直到應用程序再次發出請求, 掛起的線程會再次被使用, 這種方式節省了大量的系統資源開銷。
newFixedThreadPool
創建線程數量只有一個的線程池,它只會用唯一的工作線程來執行任務,保證所有任務按照指定 順序(FIFO, LIFO, 優先級)執行
public static ScheduledExecutorService newSingleThreadScheduledExecutor() {
return new DelegatedScheduledExecutorService
(new ScheduledThreadPoolExecutor(1));
}
public ScheduledThreadPoolExecutor(int corePoolSize) {
super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
new DelayedWorkQueue());
}
newFixedThreadPool
用途: newFixedThreadPool用於負載較大的服務器, 需要限制線程數量, 便於資源的合理利用
public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory){
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>(),
threadFactory);
}
newFixedThreadPool線程池的參數說明
- corePoolSize、maximumPoolSize線程數都是指定的nThreads, 當線程數量大於核心線程數之後會放入阻塞隊列中
- keepAliveTime=0, 超出核心線程數量外的線程存活時間爲0, 也就是立即銷燬
- 阻塞隊列是LinkedBlockingQueue, 使用的是默認構造函數, 其容量大小是Integer,MAX_VALUE, 相對於沒有限制, 這可能導致過度創建任務, 造成系統資源問題
newFixedThreadPool執行流程
- 執行線程數小於corePoolSize時,創建新線程執行任務
- 線程數等於corePoolSize時, 將任務存入阻塞隊列中
- 阻塞隊列LinkedBlockingQueue容量是無限制的, 新建的任務會一直存入阻塞隊列中
- 執行線程會一直從阻塞隊列中獲取任務來執行
newCachedThreadPool
原理: newCachedThreadPool線程池是一個可緩存的線程池, 如果線程池長度超過處理需要, 超過60s後會被回收, 如果沒有回收會繼續創建線程
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
newCachedThreadPool線程池的參數說明
- corePoolSize核心線程數大小爲0, 直接向SynchronousQueue提交任務
- maximumPoolSize最大線程數的大小爲Integer.MAX_VALUE, 緩存時間未過期內可任意創建線程, 這可能導致過度創建任務, 造成系統資源問題
- 阻塞隊列SynchronousQueue, 不存儲元素的阻塞隊列, 每個put操作必須對應一個take操作, 否則不能繼續添加元素
newCachedThreadPool執行流程
- 沒有核心線程數, 直接向SynchronousQueue提交任務
- 如果有空閒線程, 取出任務執行, 如果沒有空閒線程, 新建一個新線程
- 執行完任務的線程有60s的存活時間, 如果存活期間內沒有新的任務會被銷燬
四、線程池原理分析
在進行原理分析之前, 再整理一下關鍵api的作用
- Executers, 工具類, 主要用來創建線程池對象
- ThreadPoolExecutor, 線程池的核心, 提供線程池的實現
- ScheduledThreadPoolExecutor, 繼承自ThreadPoolExecutor, 支持定時、週期任務
4.1 線程執行入口execute
public void execute(Runnable command) {
if (command == null) // 執行任務不能爲空, 如果爲空拋出異常
throw new NullPointerException();
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) { // 當前執行的線程數量比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); // 如果創建線程失敗, 拒絕任務(線程池已經滿了或線程池已經關閉)
}
- 程序會先檢測任務是否爲空, 如果爲空拋出異常
- 如果當前執行線程數 < 核心線程數, 創建一個新的線程
- 如果當前執行線程數=核心線程數, 將任務添加到阻塞隊列, 任務添加到隊列後再次檢測是否需要添加新的線程
- 如果線程處於非運行狀態, 且從阻塞隊列移除成功, 則拒絕該任務
- 如果線程已經被銷燬了, 新建一個新的線程來執行任務
- 如果核心線程數、阻塞隊列已滿, 則創建一個新的線程執行任務, 如果創建失敗說明線程池已經被關閉了或整個線程池已經滿了, 拒絕任務,
4.2 創建工作線程addWorker
如果工作數小於核心線程數時, 調用addWorker創建新的線程執行任務
private boolean addWorker(Runnable firstTask, boolean core) {
retry: //goto語句, 不常用的跳出循環方式, 避免死循環
for (;;) {//核心是對worker線程數 + 1
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
/**
* 1. 線程處於非運行狀態, 添加新任務, 拒絕
* 2. SHUTDOWN狀態不接受新任務, 但是對已經在隊列中的任務仍然會執行;
* 如果狀態爲SHUTDOWN, firstTask任務==null, 任務隊列不爲空時, 是允許添加新線程的, 取反表示不滿足這個條件, 也就不允許添加worker
*/
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) { // 自旋作用
int wc = workerCountOf(c); // 獲得當前工作的線程數
/**
* 1. 如果工作線程大於默認的容量 CAPACITY = (1 << (Integer.SIZE - 3)) - 1, 不能繼續添加worker
* 2. 如果工作線程大於核心線程數(core=true)或大於最大線程數(core=false), 不能繼續添加worker
*/
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
if (compareAndIncrementWorkerCount(c)) // 通過CAS設置工作線程數, 如果失敗跳到retry重試
break retry;
c = ctl.get(); // Re-read ctl
if (runStateOf(c) != rs) //再次獲取狀態, 如果和歷史狀態不一樣,說明狀態發生了變更, 需要重試
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 核心是創建工作線程Worker
boolean workerStarted = false; //工作線程是否已經啓動的標識
boolean workerAdded = false; //工作線程是否已經添加的標識
Worker w = null;
try {
w = new Worker(firstTask); // 創建Worker對象, firstTask作爲參數
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock; // 獲得重入鎖, 避免併發問題
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
/**
* 滿足添加worker到workers集合的條件
* 1. 線程池處於運行狀態
* 2. 線程池處於SHUTDOWN, 且firstTask == null
*/
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
//剛創建的線程還沒有start, 但是已經是alive狀態, 說明存在問題, 拋出異常
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
workers.add(w); // 添加worker線程到workers集合中
int s = workers.size();
if (s > largestPoolSize) //如果集合中工作線程數 > 最大線程數(largestPoolSize表示上次最大線程數), 更新最大線程數
largestPoolSize = s;
workerAdded = true; // 表示worker線程創建成功了
}
} finally {
mainLock.unlock(); // 釋放持有的鎖
}
if (workerAdded) { // worker線程創建成功, 啓動線程, 並設置線程啓動狀態爲true
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w); //如果線程添加失敗, 移除剛創建worker, 和workers.add(w)相反的操作
}
return workerStarted; // 返回創建結果
}
Worker
在上面分析addWorker代碼邏輯中, 創建Worker的邏輯如下,它僅僅是構造了一個Worker, 並且把firstTask封裝到worker中,
w = new Worker(firstTask);
首先看下Worker源碼
private final class Worker
extends AbstractQueuedSynchronizer
implements Runnable
{
/**
* This class will never be serialized, but we provide a
* serialVersionUID to suppress a javac warning.
*/
private static final long serialVersionUID = 6138294804551838833L;
/** Thread this worker is running in. Null if factory fails. */
final Thread thread;
/** Initial task to run. Possibly null. */
Runnable firstTask;
/** Per-thread task counter */
volatile long completedTasks;
/**
* Creates with given first task and thread from ThreadFactory.
* @param firstTask the first task (null if none)
*/
Worker(Runnable firstTask) {
setState(-1); // inhibit interrupts until runWorker
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
/** Delegates main run loop to outer runWorker */
public void run() {
runWorker(this);
}
// Lock methods
//
// The value 0 represents the unlocked state.
// The value 1 represents the locked state.
protected boolean isHeldExclusively() {
return getState() != 0;
}
protected boolean tryAcquire(int unused) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int unused) {
setExclusiveOwnerThread(null);
setState(0);
return true;
}
public void lock() { acquire(1); }
public boolean tryLock() { return tryAcquire(1); }
public void unlock() { release(1); }
public boolean isLocked() { return isHeldExclusively(); }
void interruptIfStarted() {
Thread t;
if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
try {
t.interrupt();
} catch (SecurityException ignore) {
}
}
}
}
-
Worker是final修飾的類, 繼承自AQS類, 並且實現了Runnable接口 ,創建的每個worker都是一個線程, 包含的firstTask是初始化是被執行的任務
-
thread字段是真正處理task的核心線程, firstWorker需要執行的task
-
thread通過工廠方法創建, 以當前對象作爲參數
this.thread = getThreadFactory().newThread(this);
-
run方法內部調用的是runWorker方法, 它是執行處理邏輯的核心方法
-
AQS的作用是實現了獨佔鎖的功能
//1. 獲取鎖 protected boolean tryAcquire(int unused) { if (compareAndSetState(0, 1)) { setExclusiveOwnerThread(Thread.currentThread()); return true; } return false; } //2. 釋放鎖 protected boolean tryRelease(int unused) { setExclusiveOwnerThread(null); setState(0); return true; }
爲什麼不使用ReentrantLock來實現呢?
addWorker方法有重入鎖的邏輯, Worker類自身繼承自AQS, 內部tryAcquire、tryRelease方法實現了獨佔鎖的邏輯, 那爲什麼ReentrantLock允許重入, 而tryAcquire不允許重入呢 ?
lock方法獲取了獨佔鎖之後,表示當前線程處於執行任務狀態中, 其具有下面的作用
- 如果正在執行任務,則不應該中斷線程
- 如果該線程現在不是獨佔鎖的狀態,也就是空閒的狀態,說明它沒有在處理任務,這時可以對該線程進行中斷
- 線程池在執行 shutdown 方法或 tryTerminate 方法時會調用 interruptIdleWorkers 方法來 中斷空閒的線程,interruptIdleWorkers 方法會使用 tryLock 方法來判斷線程池中的線程 是否是空閒狀態
- 之所以設置爲不可重入,是因爲我們不希望任務在調用像 setCorePoolSize 這樣的線程池 控制方法時重新獲取鎖,這樣會中斷正在運行的線程
addWorkerFailed
如果Worker線程啓動失敗,在finally中做後續處理, 這個方法的主要作用是
- 如果worker已經添加到workers集合中, 從集合中移除
- CAS方式減少工作線程數
- 嘗試結束線程池
private void addWorkerFailed(Worker w) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
if (w != null)
workers.remove(w);
decrementWorkerCount();
tryTerminate();
} finally {
mainLock.unlock();
}
}
4.3 線程核心處理邏輯runWorker
final void runWorker(Worker w) {
Thread wt = Thread.currentThread();
Runnable task = w.firstTask;
w.firstTask = null;
/**
* 表示當前worker線程允許中斷, new Worker()默認的state=-1, 而中斷需要state>=0, unlock的執行最後會調用Worker的tryRelease方法, 設置state=0, 保證interruptIfStarted方法可以正常執行
*/
w.unlock(); // allow interrupts
boolean completedAbruptly = true;
try {
// 如果task不爲空, 使用當前線程執行, 如果task爲空, 通過getTask()方法獲取任務
// 這裏實現了線程複用功能
while (task != null || (task = getTask()) != null) {
//加鎖, 保證在shutDown()時不終止正在運行的worker
w.lock();
/**
* 因爲線程爲stop狀態時, 不接受新任務, 不執行已經加入任務隊列的任務,
* 需要對STOP及以上的狀態線程進行中斷
* !wt.isInterrupted() 再一次檢查確保線程需要設置中斷標識位
*/
if ((runStateAtLeast(ctl.get(), STOP) ||
(Thread.interrupted() &&
runStateAtLeast(ctl.get(), STOP))) &&
!wt.isInterrupted())
wt.interrupt();
try {
beforeExecute(wt, task); // 沒有實現
Throwable thrown = null;
try {
task.run(); // 執行任務
} catch (RuntimeException x) {
thrown = x; throw x;
} catch (Error x) {
thrown = x; throw x;
} catch (Throwable x) {
thrown = x; throw new Error(x);
} finally {
afterExecute(task, thrown); // 沒有實現
}
} finally {
task = null; // 置空task, 下次循環通過getTask()獲取新的task
w.completedTasks++;
w.unlock();
}
}
completedAbruptly = false;
} finally {
/**
* 1. 將woker從workers中移除
* 2. completedAbruptly確定是否補充新的Worker進入workers
*/
processWorkerExit(w, completedAbruptly);
}
}
getTask
runWorker利用了線程複用功能, 執行task, task執行結束後會置空,然後通過getTask獲取新的任務進行執行,下面是獲取新task的邏輯
private Runnable getTask() {
boolean timedOut = false; // Did the last poll() time out?
for (;;) { // 自旋
int c = ctl.get();
int rs = runStateOf(c);
// Check if queue empty only if necessary.
/**
* 對線程狀態進行判斷,看是否需要對worker計數減1
* 1. 線程狀態爲shutdown, 且workQueue爲空(shutdown狀態時如果workQueue不爲空, 需要繼續執行)
* 2. 如果線程池狀態 >= stop
*/
if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {
decrementWorkerCount();
return null;
}
int wc = workerCountOf(c);
// Are workers subject to culling?
/**
* timed變量用於控制判斷是否需要進行超時控制, 對於超過核心線程數量的線程, 需要進行超時控制
* 1. allowCoreThreadTimeOut 默認false, 核心線程不允許進行超時
* 2. wc > corePoolSize 表示當前線程池中線程數量 > 核心
*/
boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
/**
* 1. 工作線程wc > maximumPoolSize可能是線程池運行時調用了 setMaximumPoolSize()方法改變了大小
* 2. timed && timedOut 如果爲true, 表示當前操作需要進行超時控制, 並且上次從阻塞隊列中獲取任務發生了超時, 體現了空閒線程的存活時間
*/
if ((wc > maximumPoolSize || (timed && timedOut))
&& (wc > 1 || workQueue.isEmpty())) {
if (compareAndDecrementWorkerCount(c))
return null;
continue;
}
try {
// 根據timed來判斷, 如果timed爲true, 通過poll(time,unit)控制超時, 否則通過take阻塞的方式獲取隊列中任務
Runnable r = timed ?
workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
workQueue.take();
if (r != null)
return r; // 如果拿到的任務不爲空, 返回獲取到的任務
timedOut = true;
} catch (InterruptedException retry) {
timedOut = false; // 如果獲取任務時, 當前線程發生了中斷, 設置timeOUt=false, 繼續重試
}
}
}
結合4.1節, 在執行 execute 方法時,如果當前線程池的線程數量超過了 corePoolSize 且小於 maximumPoolSize,並且 workQueue 已滿時,則可以增加工作線程,如果超時沒有獲取到任務,也就是 timedOut 爲 true 的情況,說明 workQueue 已經爲空了,也就說明了 當前線程池中不需要那麼多線程來執行任務了,可以把多於 corePoolSize 數量的線程銷燬掉,保持線程數量等於corePoolSize 即可。
processWorkerExit
runWorker 的 while 循環執行完畢以後,在 finally 中會調用 processWorkerExit,來銷燬工作線程
什麼時候會銷燬?
runWorker 方法執行完之後,也就是 Worker 中的 run 方法執行完,由 JVM 自動回收
4.4 拒絕策略
Executors創建線程池默認的拒絕策略是AbortPlocy, 也可以通過RejectedExecutionHandler handler參數直接指定, 拒絕策略包含下面幾種情況
private static final RejectedExecutionHandler defaultHandler =
new AbortPolicy();
- AbortPolicy:直接拋出異常,默認策略
- CallerRunsPolicy:用調用者所在的線程來執行任務
- DiscardOldestPolicy:丟棄阻塞隊列中最前的任務,並執行當前任務
- DiscardPolicy:直接丟棄任務
4.5 線程池注意事項
- 線程池的構建不允許使用 Executors 去創建,而是通過 ThreadPoolExecutor 的方式
Executors 讓用戶不需要關心線程池的參數配置, 但是使用不當可能會帶來OOM問題。
1)newFixdThreadPool 或者 singleThreadPool允許的隊列長度爲 Integer.MAX_VALUE,如果使用不當會導致大量請求堆積到隊列中導致 OOM 的風險。
2) newCachedThreadPool,允許創建線程數量爲 Integer.MAX_VALUE,這也可能會導致大量線程的創建出現 CPU 使用過高或者 OOM 的問題。
- 如何合理配置線程池的大小
1. 需要分析線程池執行的任務的特性: CPU 密集型還是IO密集型
2. 每個任務執行的平均時長,任務的執行時長是否涉及到網絡傳輸或底層系統資源
- 如果是CPU密集型, 響應時間很快,CPU一直執行, 利用率很高, 線程數的配置應儘量根據系統核數相匹配
- 如果是IO密集型函數, 主要是IO操作, CPU長時間處於空閒狀態, CPU利用率不高,可以通過計算時長來評估, CPU數目 * (線程池等待時長 + 線程池執行時長)/線程池執行時長
- 如何提前初始化線程
在3.3節中我們分析了創建ThreadPoolExecutor邏輯, 線程池創建之後, 線程池中沒有線程, 需要提交任務之後纔會創建線程。但是比較常見的情況是, 線程池創建之後需要立即創建線程,那麼有什麼方法可以立即創建線程呢 ?
- prestartCoreThread(),初始化一個核心線程
- prestartAllCoreThread(),初始化所有核心線程
- 動態調整線程池容量
ThreadPoolExecutor提供了動態調整線程池容量大小方法
- setCorePoolSize(), 設置核心線程池大小
- setMaximumPoolSize(), 設置最大線程數目
- beforeExecute、afterExecute的應用
五、Callable/Future原理分析
在4.1 節中我們提到了執行任務的方法execute, 除了execute外, 還有submit方法用於執行任務, 那麼這兩種方法有什麼區別呢 ?
- execute只接收Runnable作爲參數, submit可以接收Runnable、Callable作爲參數
- execute如果出現問題會拋出異常, submit沒有異常拋出, 除非調用Future.get()
- execute沒有返回值, submit有返回值, 返回值類型是Future
5.1 Callable/Future原理分析
Callable
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
Callable是一個函數式接口, 只有一個帶有返回值的call方法, 子類需要重寫這個方法。
FutureTask
在進行講解前, 請先看下類繼承關係圖示
FutureTask實現了RunnableFuture接口,而RunnableFuture同時繼承了Runnable、Future接口, 這使得FutureTask不僅具有線程Runnable特性,也同時具有Future特性, 那麼Future具有什麼作用呢 ?
Future表示一個任務的生命週期, 並提供了相應方法來判斷是否完成或取消,也提供了方法獲取執行結果, 下面是類代碼
public interface Future<V> {
//取消當前Future
boolean cancel(boolean mayInterruptIfRunning);
//判斷當前Future是否被取消, ture-取消, false-未取消
boolean isCancelled();
/**
* 當前Future是否已結束,執行結束情況包含:
* 運行完成、拋出異常、取消
*/
boolean isDone();
//獲取Future執行結果, 如果Future沒結束, 當前線程就等待, 直到Future結束, 再喚醒等待結果值的線程
V get() throws InterruptedException, ExecutionException;
//獲取Future的執行結果, 如果Future沒結束, 等待超時時間timeout
V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException;
}
FutureTask使用了生產者消費者模型, Runnable屬於生產者, Future屬於消費者,Runnable通過run方法計算結果, Future通過get獲取結果, 如果生產者還沒有準備好, 消費者會被阻塞, 當生產者準備好後會喚醒消費者繼續執行。
5.2 FutureTask狀態分類
private volatile int state;
//NEW 新建狀態,表示這個 FutureTask 還沒有開始運行
private static final int NEW = 0;
// COMPLETING 完成狀態, 表示 FutureTask 任務已經計算完畢了, 但是還有一些後續操作,例如喚醒等待線程操作,還沒有完成
private static final int COMPLETING = 1;
// FutureTask任務完結,正常完成,沒有發生異常
private static final int NORMAL = 2;
// FutureTask任務完結,因爲發生異常
private static final int EXCEPTIONAL = 3;
// FutureTask任務完結,因爲取消任務
private static final int CANCELLED = 4;
// FutureTask任務完結,也是取消任務,不過發起了中斷運行任務線程的中斷請求
private static final int INTERRUPTING = 5;
// FutureTask任務完結,也是取消任務,已經完成了中斷運行任務線程的中斷請求
private static final int INTERRUPTED = 6;
5.3 run方法執行
public void run() {
/**
* 如果狀態state不是NEW, 或者設置runner值失敗,
* 表示有別的線程在此之前調用 run 方法,併成功設置了 runner 值
*/
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) { // 只有c不爲null且狀態state爲NEW的情況
V result;
boolean ran;
try {
result = c.call(); // 調用Callable的call方法執行邏輯
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex); // 設置異常結果
}
if (ran)
set(result); // 無異常, 設置結果result
}
} finally {
// runner must be non-null until state is settled to
// prevent concurrent calls to run()
runner = null;
// state must be re-read after nulling runner to prevent
// leaked interrupts
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
run方法實現是調用Callable的call方法, 返回結果值result, 根據是否發生異常, 調用set(result)或setException(ex)方法表示FutureTask完成
5.4 get方法執行
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING) // 判斷當前狀態 <= COMPLETING, 表示熱舞還沒有結束
s = awaitDone(false, 0L); // 還沒有結束, 進行等待
return report(s); // report返回結果
}
public V get(long timeout, TimeUnit unit)
throws InterruptedException, ExecutionException, TimeoutException {
if (unit == null)
throw new NullPointerException();
int s = state;
if (s <= COMPLETING &&
(s = awaitDone(true, unit.toNanos(timeout))) <= COMPLETING)
throw new TimeoutException();
return report(s);
}
get方法是以阻塞的方式獲取執行結果
- 判斷當前狀態, 如果狀態小於等於COMPLETING,表示FutureTask任務還沒有完結, 會調用awaitDone方法, 讓當前線程等待
- report返回結果或者拋出異常
5.5 awaitDone方法執行
private int awaitDone(boolean timed, long nanos)
throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false; // queued表示節點是否已經添加, false-否, true-是
for (;;) { // 自旋
// 如果當前線程的中斷標識是true, 從列表移除節點q, 並拋出InterruptedException異常
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) { // 當狀態大於 COMPLETING 時,表示 FutureTask 任務已結束
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING) // 表示還有一些後序操作沒有完成,當前線程讓出執行權
Thread.yield();
else if (q == null) // 表示狀態NEW,當前線程需要阻塞等待, 將它插入等待線程鏈表中
q = new WaitNode();
else if (!queued) // 使用CAS函數將新節點添加到鏈表中,如果添加失敗,那麼queued爲false, 下次循環時會繼續添加, 直到成功
queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
q.next = waiters, q);
else if (timed) {// timed是bool值, 爲ture時表示需要設置超時情況
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos); // 調用parkNanos方法等待nanos時間,被阻塞的線程在等到run方法執行結束後被喚醒
}
else
LockSupport.park(this);
}
}
被阻塞的線程在等到run方法執行結束後被喚醒
5.6 report方法執行
report方法根據傳入狀態值s, 來決定是拋出異常還是返回結果值, 這兩個情況都是表示FutureTask完結了
private V report(int s) throws ExecutionException {
Object x = outcome; // 表示call返回值
if (s == NORMAL) // 表示正常完結狀態, 返回結果值
return (V)x;
/**
* >= CANCELLED, 表示手動取消FutureTask任務, 會拋出CancellationException異常
*/
if (s >= CANCELLED)
throw new CancellationException();
// 如果不是上面的情況, 說明發生了異常, 這裏需要拋出異常
throw new ExecutionException((Throwable)x);
}
六、線程池原理流程圖
經過上面的分析, 這裏我們對線程池原理概括如下圖示