前言
上一篇內容寫了Java
中執行緒池的實現原理及原始碼分析,說好的是實實在在的大滿足,想通過一篇文章讓大家對執行緒池有個透徹的瞭解,但是文章寫完總覺得還缺點什麼?
上篇文章只提到執行緒提交的execute()
方法,並沒有講解執行緒提交的submit()
方法,submit()
有一個返回值,可以獲取執行緒執行的結果Future<T>
,這一講就那深入學習下submit()
和FutureTask
實現原理。
使用場景&示例
使用場景
我能想到的使用場景就是在平行計算的時候,例如一個方法中呼叫methodA()、methodB()
,我們可以通過執行緒池非同步去提交方法A、B,然後在主執行緒中獲取組裝方法A、B計算後的結果,能夠大大提升方法的吞吐量。
使用示例
/**
* @author wangmeng
* @date 2020/5/28 15:30
*/
public class FutureTaskTest {
public static void main(String[] args) throws InterruptedException, ExecutionException {
ExecutorService threadPool = Executors.newCachedThreadPool();
System.out.println("====執行FutureTask執行緒任務====");
Future<String> futureTask = threadPool.submit(new Callable<String>() {
@Override
public String call() throws Exception {
System.out.println("FutureTask執行業務邏輯");
Thread.sleep(2000);
System.out.println("FutureTask業務邏輯執行完畢!");
return "歡迎關注: 一枝花算不算浪漫!";
}
});
System.out.println("====執行主執行緒任務====");
Thread.sleep(1000);
boolean flag = true;
while(flag){
if(futureTask.isDone() && !futureTask.isCancelled()){
System.out.println("FutureTask非同步任務執行結果:" + futureTask.get());
flag = false;
}
}
threadPool.shutdown();
}
}
上面的使用很簡單,submit()
內部傳遞的實際上是個Callable
介面,我們自己實現其中的call()
方法,我們通過futureTask
既可以獲取到具體的返回值。
submit()實現原理
submit()
是也是提交任務到執行緒池,只是它可以獲取任務返回結果,返回結果是通過FutureTask
來實現的,先看下ThreadPoolExecutor
中程式碼實現:
public class ThreadPoolExecutor extends AbstractExecutorService {
public <T> Future<T> submit(Callable<T> task) {
if (task == null) throw new NullPointerException();
RunnableFuture<T> ftask = newTaskFor(task);
execute(ftask);
return ftask;
}
}
public abstract class AbstractExecutorService implements ExecutorService {
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
return new FutureTask<T>(callable);
}
}
提交任務還是執行execute()
方法,只是task
被包裝成了FutureTask
,也就是在excute()
中啟動執行緒後會執行FutureTask.run()
方法。
再來具體看下它執行的完整鏈路圖:
上圖可以看到,執行任務並返回執行結果的核心邏輯實在FutureTask
中,我們以FutureTask.run/get
兩個方法為突破口,一點點剖析FutureTask
的實現原理。
FutureTask原始碼初探
先看下FutureTask
中部分屬性:
public class FutureTask<V> implements RunnableFuture<V> {
private volatile int state;
private static final int NEW = 0;
private static final int COMPLETING = 1;
private static final int NORMAL = 2;
private static final int EXCEPTIONAL = 3;
private static final int CANCELLED = 4;
private static final int INTERRUPTING = 5;
private static final int INTERRUPTED = 6;
private Callable<V> callable;
private Object outcome;
private volatile Thread runner;
private volatile WaitNode waiters;
}
- state
當前task狀態,共有7中型別。
NEW: 當前任務尚未執行
COMPLETING: 當前任務正在結束,尚未完全結束,一種臨界狀態
NORMAL:當前任務正常結束
EXCEPTIONAL: 當前任務執行過程中發生了異常。
CANCELLED: 當前任務被取消
INTERRUPTING: 當前任務中斷中..
INTERRUPTED: 當前任務已中斷
- callble
使用者提交任務傳遞的Callable,自定義call方法,實現業務邏輯
- outcome
任務結束時,outcome儲存執行結果或者異常資訊。
- runner
當前任務被執行緒執行期間,儲存當前任務的執行緒物件引用
- waiters
因為會有很多執行緒去get當前任務的結果,所以這裡使用了一種stack資料結構來儲存
FutureTask.run()實現原理
我們已經知道線上程池runWorker()
中最終會呼叫到FutureTask.run()
方法中,我們就來看下它的執行原理吧:
具體程式碼如下:
public class FutureTask<V> implements RunnableFuture<V> {
public void run() {
if (state != NEW ||
!UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread()))
return;
try {
Callable<V> c = callable;
if (c != null && state == NEW) {
V result;
boolean ran;
try {
result = c.call();
ran = true;
} catch (Throwable ex) {
result = null;
ran = false;
setException(ex);
}
if (ran)
set(result);
}
} finally {
runner = null;
int s = state;
if (s >= INTERRUPTING)
handlePossibleCancellationInterrupt(s);
}
}
}
首先是判斷FutureTask
中state
狀態,必須是NEW
才可以繼續執行。
然後通過CAS
修改runner
引用為當前執行緒。
接著執行使用者自定義的call()
方法,將返回結果設定到result中,result可能為正常返回也可能為異常資訊。這裡主要是呼叫set()/setException()
FutureTask.set()實現原理
set()
方法的實現很簡單,直接看下程式碼:
public class FutureTask<V> implements RunnableFuture<V> {
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL);
finishCompletion();
}
}
}
將call()
返回的資料賦值給全域性變數outcome
上,然後修改state
狀態為NORMAL
,最後呼叫finishCompletion()
來做掛起執行緒的喚醒操作,這個方法等到get()
後面再來講解。
FutureTask.get()實現原理
接著看下程式碼:
public class FutureTask<V> implements RunnableFuture<V> {
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L);
return report(s);
}
}
如果FutureTask
中state
為NORMAL
或者COMPLETING
,說明當前任務並沒有執行完成,呼叫get()
方法會被阻塞,具體的阻塞邏輯在awaitDone()
方法:
private int awaitDone(boolean timed, long nanos) throws InterruptedException {
final long deadline = timed ? System.nanoTime() + nanos : 0L;
WaitNode q = null;
boolean queued = false;
for (;;) {
if (Thread.interrupted()) {
removeWaiter(q);
throw new InterruptedException();
}
int s = state;
if (s > COMPLETING) {
if (q != null)
q.thread = null;
return s;
}
else if (s == COMPLETING)
Thread.yield();
else if (q == null)
q = new WaitNode();
else if (!queued)
queued = UNSAFE.compareAndSwapObject(this, waitersOffset, q.next = waiters, q);
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
這個方法可以說是FutureTask
中最核心的方法了,一步步來分析:
如果timed
不為空,這說明指定nanos
時間還未返回結果,執行緒就會退出。
q
是一個WaitNode
物件,是將當前引用執行緒封裝在一個stack
資料結構中,WaitNode
物件屬性如下:
static final class WaitNode {
volatile Thread thread;
volatile WaitNode next;
WaitNode() { thread = Thread.currentThread(); }
}
接著判斷當前執行緒是否中斷,如果中斷則丟擲中斷異常。
下面就進入一輪輪的if... else if...
判斷邏輯,我們還是採用分支的方式去分析。
分支一:if (s > COMPLETING) {
此時get()
方法已經有結果了,無論是正常返回的結果,還是異常、中斷、取消等,此時直接返回state
狀態,然後執行report()
方法。
分支二:else if (s == COMPLETING)
條件成立,說明當前任務接近完成狀態,這裡讓當前執行緒再釋放cpu
,進行下一輪搶佔cpu
。
分支三:else if (q == null)
第一次自旋執行,WaitNode
還沒有初始化,初始化q=new WaitNode();
分支四:else if (!queued){
queued
代表當前執行緒是否入棧,如果沒有入棧則進行入棧操作,順便將全域性變數waiters
指向棧頂元素。
分支五/六:LockSupport.park
如果設定了超時時間,則使用parkNanos
來掛起當前執行緒,否則使用park()
經過這麼一輪自旋迴圈後,如果執行call()
還沒有返回結果,那麼呼叫get()
方法的執行緒都會被掛起。
被掛起的執行緒會等待run()
返回結果後依次喚醒,具體的執行邏輯在finishCompletion()
中。
最終stack
結構中資料如下:
FutureTask.finishCompletion()實現原理
具體實現程式碼如下:
private void finishCompletion() {
for (WaitNode q; (q = waiters) != null;) {
if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
for (;;) {
Thread t = q.thread;
if (t != null) {
q.thread = null;
LockSupport.unpark(t);
}
WaitNode next = q.next;
if (next == null)
break;
q.next = null;
q = next;
}
break;
}
}
done();
callable = null;
}
程式碼實現很簡單,看過get()
方法後,我們知道所有呼叫get()
方法的執行緒,在run()
還沒有返回結果前,都會儲存到一個有WaitNode
構成的statck
資料結構中,而且每個執行緒都會被掛起。
這裡是遍歷waiters
棧頂元素,然後依次查詢起next
節點進行喚醒,喚醒後的節點接著會往後呼叫report()
方法。
FutureTask.report()實現原理
具體程式碼如下:
private V report(int s) throws ExecutionException {
Object x = outcome;
if (s == NORMAL)
return (V)x;
if (s >= CANCELLED)
throw new CancellationException();
throw new ExecutionException((Throwable)x);
}
這個方法很簡單,因為執行到了這裡,說明當前state
狀態肯定大於COMPLETING
,判斷如果是正常返回,那麼返回outcome
資料。
如果state
是取消狀態,丟擲CancellationException
異常。
如果狀態都不滿足,則說明執行中出現了差錯,直接丟擲ExecutionException
FutureTask.cancel()實現原理
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try {
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally {
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
cancel()
方法的邏輯很簡單,就是修改state
狀態為CANCELLED
,然後呼叫finishCompletion()
來喚醒等待的執行緒。
這裡如果mayInterruptIfRunning
,就會先中斷當前執行緒,然後再去喚醒等待的執行緒。
總結
FutureTask
的實現原理其實很簡單,每個方法基本上都畫了一個簡單的流程圖來方便立即。
後面還打算分享一個BlockingQueue
相關的原始碼解讀,這樣執行緒池也可以算是完結了。
在這之前可能會先分享一個SpringCloud
常見配置程式碼分析、最佳實踐等手冊,方便工作中使用,也是對之前看過的原始碼一種總結。敬請期待!
歡迎關注: