本文将主要講解 J.U.C 中的 Future 架構,并分析結合源碼分析其内部結構邏輯;
一、Future 架構概述
JDK 中的 Future 架構實際就是 Future 模式的實作,通常情況下我們會配合線程池使用,但也可以單獨使用;下面我們就單獨使用簡單舉例;
-
應用執行個體
FutureTask future = new FutureTask<>(() -> {
log.info(“異步任務執行…”);
Thread.sleep(2000);
log.info(“過了很久很久…”);
return “異步任務完成”;
});
log.info(“啟動異步任務…”);
new Thread(future).start();
log.info(“繼續其他任務…”);
Thread.sleep(1000);
log.info(“擷取異步任務結果:{}”, future.get());
列印:
[15:38:03,231 INFO ] [main] - 啟動異步任務…
[15:38:03,231 INFO ] [main] - 繼續其他任務…
[15:38:03,231 INFO ] [Thread-0] - 異步任務執行…
[15:38:05,232 INFO ] [Thread-0] - 過了很久很久…
[15:38:05,236 INFO ] [main] - 擷取異步任務結果:異步任務完成
如上面代碼所示,首先我們将要執行的任務包裝成 Callable,這裡如果不需要傳回值也可以使用 Runnable;然後建構 FutureTask 由一個線程啟動,最後使用 Future.get() 擷取異步任務結果;
-
Future 運作邏輯
對于 Future 模式的流程圖如下:

對比上面的執行個體代碼,大家可能會發現有些不一樣,因為在 FutureTask 同時繼承了 Runnable 和 Future 接口,是以再送出任務後沒有傳回Future,而是直接使用自身調用 get;下面我們就對源碼進行實際分析;
二、源碼分析
-
FutureTask 主體結構
public interface RunnableFuture extends Runnable, Future {}
public class FutureTask implements RunnableFuture {
private volatile int state; // 任務運作狀态
private Callable callable; // 異步任務
private Object outcome; // 傳回結果
private volatile Thread runner; // 異步任務執行線程
private volatile WaitNode waiters; // 等待異步結果的線程棧(通過Treiber stack算法實作)
public FutureTask(Callable callable) { // 需要傳回值
if (callable == null)
throw new NullPointerException();
this.callable = callable;
this.state = NEW; // ensure visibility of callable
}
public FutureTask(Runnable runnable, V result) {
this.callable = Executors.callable(runnable, result);
this.state = NEW; // ensure visibility of callable
}
…
}
另外在代碼中還可以看見有很多地方都是用了 CAS 來更新變量,而 JDK1.6 中甚至使用了 AQS 來實作;其原因就是同一個 FutureTask 可以多個線程同時送出,也可以多個線程同時擷取; 是以代碼中有很多的狀态變量:
// FutureTask.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; // 任務中斷
同時源碼的注釋中也詳細給出了可能出現的狀态轉換:
NEW -> COMPLETING -> NORMAL // 任務正常執行
NEW -> COMPLETING -> EXCEPTION // 任務執行異常
NEW ->CANCELLED // 任務取消
NEW -> INITERRUPTING -> INTERRUPTED // 任務中斷
注意這裡的 COMPLETING 狀态是一個很微妙的狀态,正因為有他的存在才能實作無鎖指派;大家先留意這個狀态,然後在代碼中應該能體會到;另外這裡還有一個變量需要注意,WaitNode ;使用 Treiber stack 算法實作的無鎖棧;其原理說明可以參考下面第三節;
-
任務執行
public void run() {
if (state != NEW || // 確定任務執行完成後,不再重複執行
!UNSAFE.compareAndSwapObject(this, runnerOffset,
null, Thread.currentThread())) // 確定隻有一個線程執行
return;
try {
Callable 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); // 確定中斷狀态已經設定
}
}
// 設定異步任務結果
protected void set(V v) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { // 保證結果隻能設定一次
outcome = v;
UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
finishCompletion(); // 喚醒等待線程
}
}
protected void setException(Throwable t) {
if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) { // 保證結果隻能設定一次
outcome = t;
UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
finishCompletion();
}
}
-
任務取消
public boolean cancel(boolean mayInterruptIfRunning) {
if (!(state == NEW && // 隻有在任務執行階段才能取消
UNSAFE.compareAndSwapInt(this, stateOffset, NEW, // 設定取消狀态
mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
return false;
try { // in case call to interrupt throws exception
if (mayInterruptIfRunning) {
try {
Thread t = runner;
if (t != null)
t.interrupt();
} finally { // final state
UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
}
}
} finally {
finishCompletion();
}
return true;
}
注意 cancel(false) 也就是僅取消,并沒有打斷;異步任務會繼續執行,隻是這裡首先設定了 FutureTask.state = CANCELLED ,是以最後在設定結果的時候會失敗,UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING) ;
-
擷取結果
public V get() throws InterruptedException, ExecutionException {
int s = state;
if (s <= COMPLETING)
s = awaitDone(false, 0L); // 阻塞等待
return report(s);
}
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);
}
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); // 使用 Treiber stack 算法
else if (timed) {
nanos = deadline - System.nanoTime();
if (nanos <= 0L) {
removeWaiter(q);
return state;
}
LockSupport.parkNanos(this, nanos);
}
else
LockSupport.park(this);
}
}
三、Treiber stack
在《Java 并發程式設計實戰》中講了, 建立非阻塞算法的關鍵在于,找出如何将原子修改的範圍縮小到單個變量上,同時還要維護資料的一緻性 。
@ThreadSafe public class ConcurrentStack {
AtomicReference<Node> top = new AtomicReference<>();
private static class Node {
public final E item;
public Node next;
public Node(E item) {
this.item = item;
}
}
public void push(E item) {
Node newHead = new Node<>(item);
Node oldHead;
do {
oldHead = top.get();
newHead.next = oldHead;
} while (!top.compareAndSet(oldHead, newHead));
}
public E pop() {
Node oldHead;
Node newHead;
do {
oldHead = top.get();
if (oldHead == null)
return null;
newHead = oldHead.next;
} while (!top.compareAndSet(oldHead, newHead));
return oldHead.item;
}
}
總結
總體來講源碼比較簡單,因為其本身隻是一個 Future 模式的實作
但是其中的狀态量的設定,還有裡面很多無鎖的處理方式,才是 FutureTask 帶給我們的精華!