以兩種非同步模型應用案例,深度解析Future介面

華為雲開發者社群發表於2021-07-30
摘要:本文以實際案例的形式分析了兩種非同步模型,並從原始碼角度深度解析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類的原始碼就分析完了。

 

點選關注,第一時間瞭解華為雲新鮮技術~

相關文章