以兩種異步模型應用案例,深度解析Future接口
摘要:本文以實際案例的形式分析了兩種異步模型,並從源碼角度深度解析Future接口和FutureTask類。
本文分享自華為雲社區《【精通高並發系列】兩種異步模型與深度解析Future接口(一)!》,作者:冰 河 。
本文以實際案例的形式分析了兩種異步模型,並從源碼角度深度解析Future接口和FutureTask類,希望大家踏下心來,打開你的IDE,跟着文章看源碼,相信你一定收穫不小!
一、兩種異步模型
在Java的並發編程中,大體上會分為兩種異步編程模型,一類是直接以異步的形式來並行運行其他的任務,不需要返回任務的結果數據。一類是以異步的形式運行其他任務,需要返回結果。
1.無返回結果的異步模型
無返回結果的異步任務,可以直接將任務丟進線程或線程池中運行,此時,無法直接獲得任務的執行結果數據,一種方式是可以使用回調方法來獲取任務的運行結果。
具體的方案是:定義一個回調接口,並在接口中定義接收任務結果數據的方法,具體邏輯在回調接口的實現類中完成。將回調接口與任務參數一同放進線程或線程池中運行,任務運行後調用接口方法,執行回調接口實現類中的邏輯來處理結果數據。這裡,給出一個簡單的示例供參考。
- 定義回調接口
package io.binghe.concurrent.lab04; /** * @author binghe * @version 1.0.0 * @description 定義回調接口 */ public interface TaskCallable<T> { T callable(T t); }
便於接口的通用型,這裡為回調接口定義了泛型。
- 定義任務結果數據的封裝類
package io.binghe.concurrent.lab04; import java.io.Serializable; /** * @author binghe * @version 1.0.0 * @description 任務執行結果 */ public class TaskResult implements Serializable { private static final long serialVersionUID = 8678277072402730062L; /** * 任務狀態 */ private Integer taskStatus; /** * 任務消息 */ private String taskMessage; /** * 任務結果數據 */ private String taskResult; //省略getter和setter方法 @Override public String toString() { return "TaskResult{" + "taskStatus=" + taskStatus + ", taskMessage='" + taskMessage + '\'' + ", taskResult='" + taskResult + '\'' + '}'; } }
- 創建回調接口的實現類
回調接口的實現類主要用來對任務的返回結果進行相應的業務處理,這裡,為了方便演示,只是將結果數據返回。大家需要根據具體的業務場景來做相應的分析和處理。
package io.binghe.concurrent.lab04; /** * @author binghe * @version 1.0.0 * @description 回調函數的實現類 */ public class TaskHandler implements TaskCallable<TaskResult> { @Override public TaskResult callable(TaskResult taskResult) { //TODO 拿到結果數據後進一步處理 System.out.println(taskResult.toString()); return taskResult; } }
- 創建任務的執行類
任務的執行類是具體執行任務的類,實現Runnable接口,在此類中定義一個回調接口類型的成員變量和一個String類型的任務參數(模擬任務的參數),並在構造方法中注入回調接口和任務參數。在run方法中執行任務,任務完成後將任務的結果數據封裝成TaskResult對象,調用回調接口的方法將TaskResult對象傳遞到回調方法中。
package io.binghe.concurrent.lab04; /** * @author binghe * @version 1.0.0 * @description 任務執行類 */ public class TaskExecutor implements Runnable{ private TaskCallable<TaskResult> taskCallable; private String taskParameter; public TaskExecutor(TaskCallable<TaskResult> taskCallable, String taskParameter){ this.taskCallable = taskCallable; this.taskParameter = taskParameter; } @Override public void run() { //TODO 一系列業務邏輯,將結果數據封裝成TaskResult對象並返回 TaskResult result = new TaskResult(); result.setTaskStatus(1); result.setTaskMessage(this.taskParameter); result.setTaskResult("異步回調成功"); taskCallable.callable(result); } }
到這裡,整個大的框架算是完成了,接下來,就是測試看能否獲取到異步任務的結果了。
- 異步任務測試類
package io.binghe.concurrent.lab04; /** * @author binghe * @version 1.0.0 * @description 測試回調 */ public class TaskCallableTest { public static void main(String[] args){ TaskCallable<TaskResult> taskCallable = new TaskHandler(); TaskExecutor taskExecutor = new TaskExecutor(taskCallable, "測試回調任務"); new Thread(taskExecutor).start(); } }
在測試類中,使用Thread類創建一個新的線程,並啟動線程運行任務。運行程序最終的接口數據如下所示。
TaskResult{taskStatus=1, taskMessage='測試回調任務', taskResult='異步回調成功'}
大家可以細細品味下這種獲取異步結果的方式。這裡,只是簡單的使用了Thread類來創建並啟動線程,也可以使用線程池的方式實現。大家可自行實現以線程池的方式通過回調接口獲取異步結果。
2.有返回結果的異步模型
儘管使用回調接口能夠獲取異步任務的結果,但是這種方式使用起來略顯複雜。在JDK中提供了可以直接返回異步結果的處理方案。最常用的就是使用Future接口或者其實現類FutureTask來接收任務的返回結果。
- 使用Future接口獲取異步結果
使用Future接口往往配合線程池來獲取異步執行結果,如下所示。
package io.binghe.concurrent.lab04; import java.util.concurrent.*; /** * @author binghe * @version 1.0.0 * @description 測試Future獲取異步結果 */ public class FutureTest { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); Future<String> future = executorService.submit(new Callable<String>() { @Override public String call() throws Exception { return "測試Future獲取異步結果"; } }); System.out.println(future.get()); executorService.shutdown(); } }
運行結果如下所示。
測試Future獲取異步結果
- 使用FutureTask類獲取異步結果
FutureTask類既可以結合Thread類使用也可以結合線程池使用,接下來,就看下這兩種使用方式。
結合Thread類的使用示例如下所示。
package io.binghe.concurrent.lab04; import java.util.concurrent.*; /** * @author binghe * @version 1.0.0 * @description 測試FutureTask獲取異步結果 */ public class FutureTaskTest { public static void main(String[] args)throws ExecutionException, InterruptedException{ FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() { @Override public String call() throws Exception { return "測試FutureTask獲取異步結果"; } }); new Thread(futureTask).start(); System.out.println(futureTask.get()); } }
運行結果如下所示。
測試FutureTask獲取異步結果
結合線程池的使用示例如下。
package io.binghe.concurrent.lab04; import java.util.concurrent.*; /** * @author binghe * @version 1.0.0 * @description 測試FutureTask獲取異步結果 */ public class FutureTaskTest { public static void main(String[] args) throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newSingleThreadExecutor(); FutureTask<String> futureTask = new FutureTask<>(new Callable<String>() { @Override public String call() throws Exception { return "測試FutureTask獲取異步結果"; } }); executorService.execute(futureTask); System.out.println(futureTask.get()); executorService.shutdown(); } }
運行結果如下所示。
測試FutureTask獲取異步結果
可以看到使用Future接口或者FutureTask類來獲取異步結果比使用回調接口獲取異步結果簡單多了。注意:實現異步的方式很多,這裡只是用多線程舉例。
二、深度解析Future接口
1.Future接口
Future是JDK1.5新增的異步編程接口,其源代碼如下所示。
package java.util.concurrent; public interface Future<V> { boolean cancel(boolean mayInterruptIfRunning); boolean isCancelled(); boolean isDone(); V get() throws InterruptedException, ExecutionException; V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
可以看到,在Future接口中,總共定義了5個抽象方法。接下來,就分別介紹下這5個方法的含義。
- cancel(boolean)
取消任務的執行,接收一個boolean類型的參數,成功取消任務,則返回true,否則返回false。當任務已經完成,已經結束或者因其他原因不能取消時,方法會返回false,表示任務取消失敗。當任務未啟動調用了此方法,並且結果返回true(取消成功),則當前任務不再運行。如果任務已經啟動,會根據當前傳遞的boolean類型的參數來決定是否中斷當前運行的線程來取消當前運行的任務。
- isCancelled()
判斷任務在完成之前是否被取消,如果在任務完成之前被取消,則返回true;否則,返回false。
這裡需要注意一個細節:只有任務未啟動,或者在完成之前被取消,才會返回true,表示任務已經被成功取消。其他情況都會返回false。
- isDone()
判斷任務是否已經完成,如果任務正常結束、拋出異常退出、被取消,都會返回true,表示任務已經完成。
- get()
當任務完成時,直接返回任務的結果數據;當任務未完成時,等待任務完成並返回任務的結果數據。
- get(long, TimeUnit)
當任務完成時,直接返回任務的結果數據;當任務未完成時,等待任務完成,並設置了超時等待時間。在超時時間內任務完成,則返回結果;否則,拋出TimeoutException異常。
2.RunnableFuture接口
Future接口有一個重要的子接口,那就是RunnableFuture接口,RunnableFuture接口不但繼承了Future接口,而且繼承了java.lang.Runnable接口,其源代碼如下所示。
package java.util.concurrent; public interface RunnableFuture<V> extends Runnable, Future<V> { void run(); }
這裡,問一下,RunnableFuture接口中有幾個抽象方法?想好了再說!哈哈哈。。。
這個接口比較簡單run()方法就是運行任務時調用的方法。
3.FutureTask類
FutureTask類是RunnableFuture接口的一個非常重要的實現類,它實現了RunnableFuture接口、Future接口和Runnable接口的所有方法。FutureTask類的源代碼比較多,這個就不粘貼了,大家自行到java.util.concurrent下查看。
(1)FutureTask類中的變量與常量
在FutureTask類中首先定義了一個狀態變量state,這個變量使用了volatile關鍵字修飾,這裡,大家只需要知道volatile關鍵字通過內存屏障和禁止重排序優化來實現線程安全,後續會單獨深度分析volatile關鍵字是如何保證線程安全的。緊接着,定義了幾個任務運行時的狀態常量,如下所示。
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;
其中,代碼注釋中給出了幾個可能的狀態變更流程,如下所示。
NEW -> COMPLETING -> NORMAL NEW -> COMPLETING -> EXCEPTIONAL NEW -> CANCELLED NEW -> INTERRUPTING -> INTERRUPTED
接下來,定義了其他幾個成員變量,如下所示。
private Callable<V> callable; private Object outcome; private volatile Thread runner; private volatile WaitNode waiters;
又看到我們所熟悉的Callable接口了,Callable接口那肯定就是用來調用call()方法執行具體任務了。
- outcome:Object類型,表示通過get()方法獲取到的結果數據或者異常信息。
- runner:運行Callable的線程,運行期間會使用CAS保證線程安全,這裡大家只需要知道CAS是Java保證線程安全的一種方式,後續文章中會深度分析CAS如何保證線程安全。
- waiters:WaitNode類型的變量,表示等待線程的堆棧,在FutureTask的實現中,會通過CAS結合此堆棧交換任務的運行狀態。
看一下WaitNode類的定義,如下所示。
static final class WaitNode { volatile Thread thread; volatile WaitNode next; WaitNode() { thread = Thread.currentThread(); } }
可以看到,WaitNode類是FutureTask類的靜態內部類,類中定義了一個Thread成員變量和指向下一個WaitNode節點的引用。其中通過構造方法將thread變量設置為當前線程。
(2)構造方法
接下來,是FutureTask的兩個構造方法,比較簡單,如下所示。
public FutureTask(Callable<V> callable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; } public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; }
(3)是否取消與完成方法
繼續向下看源碼,看到一個任務是否取消的方法,和一個任務是否完成的方法,如下所示。
public boolean isCancelled() { return state >= CANCELLED; } public boolean isDone() { return state != NEW; }
這兩方法中,都是通過判斷任務的狀態來判定任務是否已取消和已完成的。為啥會這樣判斷呢?再次查看FutureTask類中定義的狀態常量發現,其常量的定義是有規律的,並不是隨意定義的。其中,大於或者等於CANCELLED的常量為CANCELLED、INTERRUPTING和INTERRUPTED,這三個狀態均可以表示線程已經被取消。當狀態不等於NEW時,可以表示任務已經完成。
通過這裡,大家可以學到一點:以後在編碼過程中,要按照規律來定義自己使用的狀態,尤其是涉及到業務中有頻繁的狀態變更的操作,有規律的狀態可使業務處理變得事半功倍,這也是通過看別人的源碼設計能夠學到的,這裡,建議大家還是多看別人寫的優秀的開源框架的源碼。
(4)取消方法
我們繼續向下看源碼,接下來,看到的是cancel(boolean)方法,如下所示。
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(boolean)方法。在cancel(boolean)方法中,首先判斷任務的狀態和CAS的操作結果,如果任務的狀態不等於NEW或者CAS的操作返回false,則直接返回false,表示任務取消失敗。如下所示。
if (!(state == NEW && UNSAFE.compareAndSwapInt(this, stateOffset, NEW, mayInterruptIfRunning ? INTERRUPTING : CANCELLED))) return false;
接下來,在try代碼塊中,首先判斷是否可以中斷當前任務所在的線程來取消任務的運行。如果可以中斷當前任務所在的線程,則以一個Thread臨時變量來指向運行任務的線程,當指向的變量不為空時,調用線程對象的interrupt()方法來中斷線程的運行,最後將線程標記為被中斷的狀態。如下所示。
try { if (mayInterruptIfRunning) { try { Thread t = runner; if (t != null) t.interrupt(); } finally { // final state UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED); } } }
這裡,發現變更任務狀態使用的是UNSAFE.putOrderedInt()方法,這個方法是個什麼鬼呢?點進去看一下,如下所示。
public native void putOrderedInt(Object var1, long var2, int var4);
可以看到,又是一個本地方法,嘿嘿,這裡先不管它,後續文章會詳解這些方法的作用。
接下來,cancel(boolean)方法會進入finally代碼塊,如下所示。
finally { finishCompletion(); }
可以看到在finallly代碼塊中調用了finishCompletion()方法,顧名思義,finishCompletion()方法表示結束任務的運行,接下來看看它是如何實現的。點到finishCompletion()方法中看一下,如下所示。
private void finishCompletion() { // assert state > COMPLETING; 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; // unlink to help gc q = next; } break; } } done(); callable = null; // to reduce footprint }
在finishCompletion()方法中,首先定義一個for循環,循環終止因子為waiters為null,在循環中,判斷CAS操作是否成功,如果成功進行if條件中的邏輯。首先,定義一個for自旋循環,在自旋循環體中,喚醒WaitNode堆棧中的線程,使其運行完成。當WaitNode堆棧中的線程運行完成後,通過break退出外層for循環。接下來調用done()方法。done()方法又是個什麼鬼呢?點進去看一下,如下所示。
protected void done() { }
可以看到,done()方法是一個空的方法體,交由子類來實現具體的業務邏輯。
當我們的具體業務中,需要在取消任務時,執行一些額外的業務邏輯,可以在子類中覆寫done()方法的實現。
(5)get()方法
繼續向下看FutureTask類的代碼,FutureTask類中實現了兩個get()方法,如下所示。
public V get() throws InterruptedException, ExecutionException { int s = state; if (s <= COMPLETING) s = awaitDone(false, 0L); return report(s); } 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()方法為當任務未運行完成時,會阻塞,直到返回任務結果。有參數的get()方法為當任務未運行完成,並且等待時間超出了超時時間,會TimeoutException異常。
兩個get()方法的主要邏輯差不多,一個沒有超時設置,一個有超時設置,這裡說一下主要邏輯。判斷任務的當前狀態是否小於或者等於COMPLETING,也就是說,任務是NEW狀態或者COMPLETING,調用awaitDone()方法,看下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) // cannot time out yet 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); } }
接下來,拆解awaitDone()方法。在awaitDone()方法中,最重要的就是for自旋循環,在循環中首先判斷當前線程是否被中斷,如果已經被中斷,則調用removeWaiter()將當前線程從堆棧中移除,並且拋出InterruptedException異常,如下所示。
if (Thread.interrupted()) { removeWaiter(q); throw new InterruptedException(); }
接下來,判斷任務的當前狀態是否完成,如果完成,並且堆棧句柄不為空,則將堆棧中的當前線程設置為空,返回當前任務的狀態,如下所示。
int s = state; if (s > COMPLETING) { if (q != null) q.thread = null; return s; }
當任務的狀態為COMPLETING時,使當前線程讓出CPU資源,如下所示。
else if (s == COMPLETING) Thread.yield();
如果堆棧為空,則創建堆棧對象,如下所示。
else if (q == null) q = new WaitNode();
如果queued變量為false,通過CAS操作為queued賦值,如果awaitDone()方法傳遞的timed參數為true,則計算超時時間,當時間已超時,則在堆棧中移除當前線程並返回任務狀態,如下所示。如果未超時,則重置超時時間,如下所示。
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);
接下來,回到get()方法中,當awaitDone()方法返回結果,或者任務的狀態不滿足條件時,都會調用report()方法,並將當前任務的狀態傳遞到report()方法中,並返回結果,如下所示。
return report(s);
看來,這裡還要看下report()方法啊,點進去看下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); }
可以看到,report()方法的實現比較簡單,首先,將outcome數據賦值給x變量,接下來,主要是判斷接收到的任務狀態,如果狀態為NORMAL,則將x強轉為泛型類型返回;當任務的狀態大於或者等於CANCELLED,也就是任務已經取消,則拋出CancellationException異常,其他情況則拋出ExecutionException異常。
至此,get()方法分析完成。注意:一定要理解get()方法的實現,因為get()方法是我們使用Future接口和FutureTask類時,使用的比較頻繁的一個方法。
(6)set()方法與setException()方法
繼續看FutureTask類的代碼,接下來看到的是set()方法與setException()方法,如下所示。
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(); } }
通過源碼可以看出,set()方法與setException()方法整體邏輯幾乎一樣,只是在設置任務狀態時一個將狀態設置為NORMAL,一個將狀態設置為EXCEPTIONAL。
至於finishCompletion()方法,前面已經分析過。
(7)run()方法與runAndReset()方法
接下來,就是run()方法了,run()方法的源代碼如下所示。
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 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); } }
可以這麼說,只要使用了Future和FutureTask,就必然會調用run()方法來運行任務,掌握run()方法的流程是非常有必要的。在run()方法中,如果當前狀態不是NEW,或者CAS操作返回的結果為false,則直接返回,不再執行後續邏輯,如下所示。
if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return;
接下來,在try代碼塊中,將成員變量callable賦值給一個臨時變量c,判斷臨時變量不等於null,並且任務狀態為NEW,則調用Callable接口的call()方法,並接收結果數據。並將ran變量設置為true。當程序拋出異常時,將接收結果的變量設置為null,ran變量設置為false,並且調用setException()方法將任務的狀態設置為EXCEPTIONA。接下來,如果ran變量為true,則調用set()方法,如下所示。
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代碼塊中,如下所示。
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); }
這裡,將runner設置為null,如果任務的當前狀態大於或者等於INTERRUPTING,也就是線程被中斷了。則調用handlePossibleCancellationInterrupt()方法,接下來,看下handlePossibleCancellationInterrupt()方法的實現。
private void handlePossibleCancellationInterrupt(int s) { if (s == INTERRUPTING) while (state == INTERRUPTING) Thread.yield(); }
可以看到,handlePossibleCancellationInterrupt()方法的實現比較簡單,當任務的狀態為INTERRUPTING時,使用while()循環,條件為當前任務狀態為INTERRUPTING,將當前線程佔用的CPU資源釋放,也就是說,當任務運行完成後,釋放線程所佔用的資源。
runAndReset()方法的邏輯與run()差不多,只是runAndReset()方法會在finally代碼塊中將任務狀態重置為NEW。runAndReset()方法的源代碼如下所示,就不重複說明了。
protected boolean runAndReset() { if (state != NEW || !UNSAFE.compareAndSwapObject(this, runnerOffset, null, Thread.currentThread())) return false; boolean ran = false; int s = state; try { Callable<V> c = callable; if (c != null && s == NEW) { try { c.call(); // don't set result ran = true; } catch (Throwable ex) { setException(ex); } } } 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 s = state; if (s >= INTERRUPTING) handlePossibleCancellationInterrupt(s); } return ran && s == NEW; }
(8)removeWaiter()方法
removeWaiter()方法中主要是使用自旋循環的方式來移除WaitNode中的線程,比較簡單,如下所示。
private void removeWaiter(WaitNode node) { if (node != null) { node.thread = null; retry: for (;;) { // restart on removeWaiter race for (WaitNode pred = null, q = waiters, s; q != null; q = s) { s = q.next; if (q.thread != null) pred = q; else if (pred != null) { pred.next = s; if (pred.thread == null) // check for race continue retry; } else if (!UNSAFE.compareAndSwapObject(this, waitersOffset, q, s)) continue retry; } break; } } }
最後,在FutureTask類的最後,有如下代碼。
// Unsafe mechanics private static final sun.misc.Unsafe UNSAFE; private static final long stateOffset; private static final long runnerOffset; private static final long waitersOffset; static { try { UNSAFE = sun.misc.Unsafe.getUnsafe(); Class<?> k = FutureTask.class; stateOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("state")); runnerOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("runner")); waitersOffset = UNSAFE.objectFieldOffset (k.getDeclaredField("waiters")); } catch (Exception e) { throw new Error(e); } }
關於這些代碼的作用,會在後續深度解析CAS文章中詳細說明,這裡就不再探討。
至此,關於Future接口和FutureTask類的源碼就分析完了。