Android Handler機制12之Callable、Future和FutureTask

Android Handler機制系列文章整體內容如下:

本片文章的主要內容如下:

  • 1、概述
  • 2、Callable
  • 3、Future
  • 4、FutureTask
  • 5、總結

一、概述

  • Java從發布的一個版本開始就可以很方便地編寫多線程的應用程序,并在設計中引入異步處理。創建線程有兩種方式,一種是直接繼承Thread,另外一種就是實現Runnable接口。Thread類、Runnable接口和Java內存管理模型使得多線程編程簡單直接。但是大家知道Thread和Runnable接口都不允許聲明檢查性異常,也不能返定義返回值。沒有返回值這點稍微有點麻煩。
  • 不能聲明拋出檢查型異常則更麻煩一些。public void run()方法契約意味著你必須捕獲并處理檢查型異常。即使你小心地保存了異常信息(在捕獲異常時)以便稍后檢查, 但也不能保證這個類(Runnnable)的所有使用者都讀取這個異常信息。你也可以修改Runnable實現的getter,讓他們都能拋出任務執行中的異常。但這種方法除了繁瑣也不是十分安全可靠,你不能強迫使用者調用這些方法,程序員很可能會調用join()方法等待線程結束后就不管了。
  • 但是現在不用擔心了,以上問題終于在1.5中解決了。Callable接口和Future接口接口的引入以及他們對線程池的支持優雅地解決了這個兩個問題。

二、Callable

Callable.java源碼地址

(一) Runnable

說到Callable就不能不說下java.lang.Runnable,它是一個接口,它只聲明了一個run()方法,由于這個run()方法的返回值是void的,所以在執行完任務之后無法返回任何結果。

public  interface Runnable {
    /**
     * When an object implementing interface <code>Runnable</code> is used
     * to create a thread, starting the thread causes the object's
     * <code>run</code> method to be called in that separately executing
     * thread.
     * <p>
     * The general contract of the method <code>run</code> is that it may
     * take any action whatsoever.
     *
     * @see     java.lang.Thread#run()
     */
    public abstract void run();
}

(二) Callable

Callable位于java.util.concurrent包下,它也是一個接口,在它里面也只聲明了一個方法,只不過這個方法叫做call()

/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * {@code call}.
 *
 * <p>The {@code Callable} interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * {@code Runnable}, however, does not return a result and cannot
 * throw a checked exception.
 *
 * <p>The {@link Executors} class contains utility methods to
 * convert from other common forms to {@code Callable} classes.
 *
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
     //
    V call() throws Exception;
}

可以看到,這一個泛型的接口,call()函數返回的類型就是傳遞進來的V類型。

(三) Callable的類注釋

為了讓我們更好的理解Callable,還是從類的注釋開始,翻譯如下:

  • 有執行結果的,并且可以引發異常的任務
    它的實現類需要去實現沒有參數的的方法,即call()方法
  • Callable有點類似于Runnable接口,因為它們都是設計成被線程去執行的可執行代碼的實例。由于Runnable是沒有返回值的,并且不能拋出一個檢查出的異常。
  • Executors類包含方法可以使得Callable轉化成其他普通形式。

(四)、 Runnable和Callable的區別:

  • 1、Runnable是Java 1.1有的,而Callable是1.5之后才加上去的
  • 2、Callable規定的方法是call(),Runnable規定的方法是run()
  • 3、Callable的任務執行后可返回值,而Runnable的任務是不能返回(因為是void)
  • 4、call()方法是可以拋出異常的,而run()方法不可以
  • 5、運行Callable任務可以拿到一個Future對象,表示異步計算的結果,它提供了檢查計算是否完成的方法,以等待計算的完成,并檢索計算的結果,通過Future對象可以了解任務的執行情況,可取消任務的執行,還可獲取執行結果
  • 6、加入線程池運行,Runnable使用ExecutorService的execute()方法,Callable使用submit()方法

三、Future

Future.java源碼地址

(一) Future類詳解

Future就是對于具體的Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果。必要時可以通過get方法獲取執行結果,該方法會阻塞知道任務返回結果。Future類位于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個方法,下面依次解釋下每個方法的作用:

  • boolean cancel(boolean mayInterruptIfRunning):方法用來取消任務,如果取消任務成功則返回true,如果取消任務失敗則返回false。參數mayInterruptIfRunning表示是否允許取消正在執行卻沒有執行完畢的任務,如果設置為true,則表示可以取消正在執行過程中任務。如果任務已經完成,則無論mayInterruptIfRunning為true還是false,此方法肯定返回false,即如果取消已完成的任務會返回false;如果任務正在執行,若mayInterruptIfRunning設置為true,則返回true,若mayInterruptIfRunning設置為false,則返回false;如果任務還沒有執行,則無論mayInterruptIfRunning為true還是false,肯定返回true。
  • boolean isCancelled():表示任務是否被取消成功,如果在任務正常完成之前取消成功則返回true.
  • isDone():方法表示任務是否已經完成,若任務完成,則返回true。
  • V get():方法用來獲取執行結果,這個方法會產生阻塞,會一直等到任務執行完畢才返回
  • V get(long timeout, TimeUnit unit):用來獲取執行結果,如果在指定時間內,還沒獲取到結果,就直接返回null。

總結一下,Future提供了三種功能:

  • 判斷任務是否完成
  • 能夠中斷任務
  • 能夠獲取任務的執行結果

(二) Future類注釋

  • Future可以表示異步計算的結果。Future提供一個方法用來檢查這個計算是否已經完成,還提供一個方法用來檢索計算結果。get()方法可以獲取計算結果,這個方法里面可能產生阻塞,如果產生了阻塞了,就阻塞到計算結束。cancel()方法可以取消執行。還有一些方法可以用來確定任務是否已經完成、是否已經取消成功了。如果任務已經執行完畢,則是不能取消的。如果你想使用Future并且,希望它是不可撤銷的,同時不關心執行的結果,可以聲明Future的泛型,并且基礎任務返回值結果為null。
  • 使用示例
   class App {
       ExecutorService executor = ...
      ArchiveSearcher searcher = ...

        void showSearch(final String target)
                throws InterruptedException {
            Future<String> future
                    = executor.submit(new Callable<String>() {
                public String call() {
                    return searcher.search(target);
                }
            });
            displayOtherThings(); // do other things while searching
            try {
                displayText(future.get()); // use future
            } catch (ExecutionException ex) {
                cleanup();
                return;
            }
        }
   }

FutureTask是Future的具體實現類,同時也實現了Runnable。所以FutureTask可以被Executor執行。例如Executor的submit方法可以換成如下寫法

FutureTask<String> future =
  new FutureTask<>(new Callable<String>() {
    public String call() {
      return searcher.search(target);
  }});
executor.execute(future);

內存一致性效應:如果想在另一個線程調用 Future.get()方法,則在調用該方法之前應該先執行其自己的操作。

(三) boolean cancel(boolean mayInterruptIfRunning)方法注釋

翻譯如下:

嘗試去關閉正在執行的任務,如果任務已經完成,或者任務已經被取消,或者任務因為某種原因而無法被取消,則關閉事失敗。當這個任務還沒有被執行,則調用此方法會成功,并且這個任務將來不會被執行。如果任務已經開始了,mayInterruptIfRunning這個入參決定是否應該中斷該任務。這個方法被執行返回后,再去調用isDone()方法,將一直返回true。如果調用這個方法后返回true,再去調用isCancelled()方法,則isCancelled()方法一直返回true。mayInterruptIfRunning這個參數表示的是該任務的線程是否可以被中斷,true表示可以中斷,如果這個任務不能被取消,則返回false,而大多數這種情況是任務已完成。

上面已經提到了Future只是一個接口,所以是無法直接用來創建對象使用的,在注釋里面推薦使用FutureTask,那我們就來看下FutureTask

四、FutureTask

FutureTask.java源碼地址
我們先來看一下FutureTask的實現:

(一)、RunnableFuture

通過代碼我們知道

public class FutureTask<V> implements RunnableFuture<V>

說明FutureTask類實現了RunnableFuture接口,我們看一下RunnableFuture接口

RunnableFuture.java源碼地址

/**
 * A {@link Future} that is {@link Runnable}. Successful execution of
 * the {@code run} method causes completion of the {@code Future}
 * and allows access to its results.
 * @see FutureTask
 * @see Executor
 * @since 1.6
 * @author Doug Lea
 * @param <V> The result type returned by this Future's {@code get} method
 */
public interface RunnableFuture<V> extends Runnable, Future<V> {
    /**
     * Sets this Future to the result of its computation
     * unless it has been cancelled.
     */
    void run();
}

通過代碼我知道RunnableFuture繼承了Runnable接口和Future接口,而FutureTask實現了RunnableFuture接口。所以它既可以作為Runnable被線程執行,也可以作為Future得到Callable的返回值

(二)、FutureTask的類注釋

/**
 * A cancellable asynchronous computation.  This class provides a base
 * implementation of {@link Future}, with methods to start and cancel
 * a computation, query to see if the computation is complete, and
 * retrieve the result of the computation.  The result can only be
 * retrieved when the computation has completed; the {@code get}
 * methods will block if the computation has not yet completed.  Once
 * the computation has completed, the computation cannot be restarted
 * or cancelled (unless the computation is invoked using
 * {@link #runAndReset}).
 *
 * <p>A {@code FutureTask} can be used to wrap a {@link Callable} or
 * {@link Runnable} object.  Because {@code FutureTask} implements
 * {@code Runnable}, a {@code FutureTask} can be submitted to an
 * {@link Executor} for execution.
 *
 * <p>In addition to serving as a standalone class, this class provides
 * {@code protected} functionality that may be useful when creating
 * customized task classes.
 *
 * @since 1.5
 * @author Doug Lea
 * @param <V> The result type returned by this FutureTask's {@code get} methods
 */

為了更好的理解作者設計,先來看下類注釋,翻譯如下:

  • 一個可以取消的異步執行,這個類是Future的基礎實現類,提供一下方法,比如可以去開啟和關閉執行,查詢是否已經執行完畢,以及檢索計算的結果。這個執行結果只能等執行完畢才能獲取,如果還未執行完畢則處于阻塞狀態。除非調用runAndReset()方法,否則一旦計算完畢后則無法重啟啟動或者取消。
  • Callable或者Runnable可以包裝FutureTask,由于FutureTask實現Runnable,所以在Executor可以執行FutureTask
  • 除了可以作為一個獨立的類外,FutureTask還提供一些protected方法,這樣在自定義任務類是,就會很方便。

(三)、FutureTask的結構

結構如下圖


FutureTask結構圖.png

繼承關系如下:

繼承關系.png

(四)、靜態final類WaitNode

    /**
     * Simple linked list nodes to record waiting threads in a Treiber
     * stack.  See other classes such as Phaser and SynchronousQueue
     * for more detailed explanation.
     */
    static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }

先翻譯一下注釋

精簡版的鏈表結構,其中每一個節點代表堆棧中等待的線程。如果想了解更多的詳細說明,請參考其他類比如Phaser和SynchronousQueue

結構如下圖


WaitNode.png

再來看下構造函數和成員變量

  • thread:代表等待的線程
  • next:代表下一個節點,通過這個節點我們也能退出這個鏈表是單向鏈表
  • 構造函數:無參的構造函數里面將thread設置為當前線程。
    總結:
    WaitNode就是一個鏈表結構,用于記錄等待當前FutureTask結果的線程。

(五)、FutureTask的狀態

FutureTask一共有7種狀態,代碼如下:

    /*
     * Revision notes: This differs from previous versions of this
     * class that relied on AbstractQueuedSynchronizer, mainly to
     * avoid surprising users about retaining interrupt status during
     * cancellation races. Sync control in the current design relies
     * on a "state" field updated via CAS to track completion, along
     * with a simple Treiber stack to hold waiting threads.
     *
     * Style note: As usual, we bypass overhead of using
     * AtomicXFieldUpdaters and instead directly use Unsafe intrinsics.
     */

    /**
     * The run state of this task, initially NEW.  The run state
     * transitions to a terminal state only in methods set,
     * setException, and cancel.  During completion, state may take on
     * transient values of COMPLETING (while outcome is being set) or
     * INTERRUPTING (only while interrupting the runner to satisfy a
     * cancel(true)). Transitions from these intermediate to final
     * states use cheaper ordered/lazy writes because values are unique
     * and cannot be further modified.
     *
     * Possible state transitions:
     * NEW -> COMPLETING -> NORMAL
     * NEW -> COMPLETING -> EXCEPTIONAL
     * NEW -> CANCELLED
     * NEW -> INTERRUPTING -> INTERRUPTED
     */
    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;

老規矩先來翻譯一下注釋,我們看到注釋有兩部分,我們依次翻譯如下:

  • 上半部分注釋:
    修訂說明:和之前版本的AbstractQueuedSynchronizer不同,主要是為了避免令人驚訝的用戶在取消競爭遷建保留中斷的狀態。在當前設計中,同步的控制是通過CAS中的更新字段——"state"來完成跟蹤的。并且通過一個Treiber堆棧來保存這些等待的線程。風格筆記(筆者注:這個真心不知道怎么翻譯,誰知道請在下面留言):和往常一樣,我們直接使用不安全的內在函數, 并且忽略使用AtomicXFieldUpdaters的開銷。

簡單的說就是,FutureTask中使用state表示任務狀態,state變更由CAS操作保證原子性。

  • 下半部分注釋:
    這個任務的運行狀態,最初是NEW狀態,在調用set()方法或者setException()方法或者cancel()方法后,運行的狀態就變為終端的狀態。在運行期間,如果計算出結果后,狀態變更為COMPLETING,如果通過調用cancel(true)安全的中斷運行,則狀態變更為INTERRUPTING。由于值是唯一且不能被進一步修改,所以從中間狀態到最終狀態的轉化是有序的。
  • 可能的狀態變更流程
  • NEW -> COMPLETING -> NORMAL
  • NEW -> COMPLETING -> EXCEPTIONAL
  • NEW -> CANCELLED
  • NEW -> INTERRUPTING -> INTERRUPTED

上面翻譯我感覺不是很好,用白話解釋一下:

FutureTask對象初始化時,在構造器把state設置為NEW,之后狀態變更依據具體執行情況來定。

  • 任務執行正常,并且還沒結束,state為COMPLETING,代表任務正在執行即將完成,接下來很快會被設置為NORMAL或者EXCEPTIONAL,這取決于調用Runnable中的call()方法是否拋出異常,沒有異常則是NORMAL,拋出異常是EXCEPTIONAL。
  • 任務提交后、任務結束前取消任務,都有可能變為CANCELLED或者INTERRUPTED。在調用cancel(boolean) 是,如果傳入false表示不中斷線程,state會變成CANCELLED,如果傳入true,則state先變為
    INTERRUPTING,中斷完成后,變為INTERRUPTED。

總結一下就是:FutureTask的狀態變化過程為,以下4種情況:

  • 任務正常執行并返回: NEW -> COMPLETING -> NORMAL
  • 任務執行中出現異常:NEW -> COMPLETING -> EXCEPTIONAL
  • 任務執行過程中被取消,并且不中斷線程:NEW -> CANCELLED
  • 任務執行過程中被取消,并且中斷線程:NEW -> INTERRUPTING -> INTERRUPTED

那我們就簡單的解釋下幾種狀態

  • NEW:任務初始化狀態
  • COMPLETING:任務正在完成狀態(任務已經執行完成,但是結果還沒有賦值給outcome)
  • NORMAL:任務完成(結果已經賦值給outcome)
  • EXCEPTIONAL:任務執行異常
  • CANCELLED:任務被取消
  • INTERRUPTING:任務被中斷中
  • INTERRUPTED:任務被中斷

(六)、FutureTask的成員變量

    /** The underlying callable; nulled out after running */
    private Callable<V> callable;
    /** The result to return or exception to throw from get() */
    private Object outcome; // non-volatile, protected by state reads/writes
    /** The thread running the callable; CASed during run() */
    private volatile Thread runner;
    /** Treiber stack of waiting threads */
    private volatile WaitNode waiters;
  • callable:任務具體執行體,具體要做的事
  • outcome:任務的執行結果,get()方法的返回值
  • runner:任務的執行線程
  • waiters:獲取任務結果的等待線程(是一個鏈式列表)

(七)、FutureTask的構造函數

FutureTask有兩個構造函數
分別是FutureTask(Callable<V> callable)和FutureTask(Runnable runnable, V result),那我們來依次分析

1、構造函數 FutureTask(Callable<V> callable)
    /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Callable}.
     *
     * @param  callable the callable task
     * @throws NullPointerException if the callable is null
     */
    public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       // ensure visibility of callable
    }

老規矩先翻譯一下注釋:

創建一個FutureTask,并在將來執行的時候,運行傳入的Callable。

看下代碼,我們知道:

  • 1、通過代碼我們知道如果傳入的Callable為空直接拋出異常,說明構造時傳入的Callable不能為空。
  • 2、設置當前狀態為NEW。

所以總結一句話就是,通過傳入Callable來構造一個FutureTask。

2、構造函數 FutureTask(Runnable runnable, V result)
    /**
     * Creates a {@code FutureTask} that will, upon running, execute the
     * given {@code Runnable}, and arrange that {@code get} will return the
     * given result on successful completion.
     *
     * @param runnable the runnable task
     * @param result the result to return on successful completion. If
     * you don't need a particular result, consider using
     * constructions of the form:
     * {@code Future<?> f = new FutureTask<Void>(runnable, null)}
     * @throws NullPointerException if the runnable is null
     */
    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }

老規矩先翻譯一下注釋:

創建一個FutureTask,并在將來執行的時候,運行傳入的Runnable,并且將成功完成后的結果返給傳入的result。

看下代碼,我們知道:

  • 1、先通過調用Executors的callable(Runnable, T)方法返回的Callable
  • 2、將上面返回的Callable指向本地變量callable
  • 3、設置當前狀態為NEW。

所以總結一句話就是,通過傳入Runnable來構造一個任務

這里順帶說下Executors.callable(runnable, result)方法的內部實現

2.1、Executors.callable(Runnable, T)
    /**
     * Returns a {@link Callable} object that, when
     * called, runs the given task and returns the given result.  This
     * can be useful when applying methods requiring a
     * {@code Callable} to an otherwise resultless action.
     * @param task the task to run
     * @param result the result to return
     * @param <T> the type of the result
     * @return a callable object
     * @throws NullPointerException if task null
     */
    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }

方法內部很簡單,就是new了一個RunnableAdapter并返回,那我們來看下RunnableAdapterr適配器

    /**
     * A callable that runs given task and returns given result.
     */
    private static final class RunnableAdapter<T> implements Callable<T> {
        private final Runnable task;
        private final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

通過上面代碼我們知道:

  • RunnableAdapter是FutureTask的一個靜態內部類并且實現了Callable,也就是說RunnableAdapter是Callable子類。
  • call方法實現代碼是,執行Runnable的run方法,并返回構造函數傳入的result參數。

這里實際上是將一個Runnable對象偽裝成一個Callable對象,是適配器對象。

3、構造函數總結

通過分析上面兩個構造函數,我們知道無論采用第一個構造函數,還是第二個構造函數,其結果都是給本地變量callable初始化賦值,所以說FutureTask最終都是執行Callable類型的任務。然后設置狀態為NEW。

(八)、FutureTask的幾個核心方法

FutureTask有幾個核心方法:

  • public void run():表示任務的執行
  • public V get()和public V get(long timeout, TimeUnit unit):表示獲取任務的結果
  • public boolean cancel(boolean mayInterruptIfRunning):表示取消任務

那我們就依次來看下

1、run()方法
    /**
     * 判斷任務的狀態是否是初始化的狀態
     * 判斷執行任務的線程對象runner是否為null,為空就將當前執行線程賦值給runner屬性
     *  不為空說明應有線程準備執行這個任務了
     */
    public void run() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, null, Thread.currentThread()))
            return;
        try {
           // 任務狀態時NEW,并且callable不為空,則執行任務
           // 如果認為被cancel了,callable會被置空
            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);
        }
    }

通過上面代碼和注釋我們知道run方法內部的流程如下:

  • 第一步:檢查當前任務是否是NEW以及runner是否為空,這一步是防止任務被取消
  • 第二步:double-check任務狀態和state
  • 第三步:執行業務邏輯,也就是c.call()方法被執行
  • 第四步:如果業務邏輯異常,則調用setException方法將異常對象賦值給outcome,并更新state的值
  • 第五步:如果業務正常,則調用set方法將執行結果賦給outcome,并更新state值。
1.1、Unsafe類

Java不能夠直接訪問操作系統底層,而是通過本地方法來訪問。Unsafe提供了硬件級別的原子訪問,主要提供以下功能:

  • 分配釋放內存
  • 定位某個字段的內存位置
  • 掛起一個線程和恢復,更多的是通過LockSupport來訪問。park和unpark
  • CAS操作,比較一個對象的某個位置的內存值是否與期望值一致。

主要方法是compareAndSwap()

1.1.1UNSAFE.compareAndSwapObject(this,runnerOffset,null, Thread.currentThread())方法

UNSAFE.compareAndSwapObject(this,RUNNER,null, Thread.currentThread())這行代碼什么意思?

compareAndSwapObject可以通過反射,根據偏移量去修改對象,第一個參數表示要修改的對象,第二個表示偏移量,第三個參數用于和偏移量對應的值進行比較,第四個參數表示如何偏移量對應的值和第三個參數一樣時要把偏移量設置成的值。

翻譯成白話文就是:

如果this對象的RUNNER偏移地址的值是null,那就把它設置為Thread.currentThread()。

上面提到了一個概念是RUNNER,那這個RUNNER 是什么東東?
我們在源碼中找到

    // Unsafe mechanics
    private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
    private static final long STATE;
    private static final long RUNNER;
    private static final long WAITERS;
    static {
        try {
            STATE = U.objectFieldOffset
                (FutureTask.class.getDeclaredField("state"));
            RUNNER = U.objectFieldOffset
                (FutureTask.class.getDeclaredField("runner"));
            WAITERS = U.objectFieldOffset
                (FutureTask.class.getDeclaredField("waiters"));
        } catch (ReflectiveOperationException e) {
            throw new Error(e);
        }

        // Reduce the risk of rare disastrous classloading in first call to
        // LockSupport.park: https://bugs.openjdk.java.net/browse/JDK-8074773
        Class<?> ensureLoaded = LockSupport.class;
    }

它對應的就是runner的成員變量,也就是說如果狀態不是NEW或者runner不是null,run方法直接返回。

所以我們知道

  • private static final long STATE:表示state這個成員變量
  • private static final long RUNNER:表示的是runner這個成員變量
  • rivate static final long WAITERS:表示的是waiters這個成員變量

在這個run方法里面分別調用了setException(Throwable )和set(V)方法,那我們就來詳細看下

1.2、setException(Throwable t)方法
    /**
     * Causes this future to report an {@link ExecutionException}
     * with the given throwable as its cause, unless this future has
     * already been set or has been cancelled.
     *
     * <p>This method is invoked internally by the {@link #run} method
     * upon failure of the computation.
     *
     * @param t the cause of failure
     */
    protected void setException(Throwable t) {
         // state狀態 NEW-> COMPLETING
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = t;
             // // COMPLETING -> EXCEPTIONAL 到達穩定狀態
            U.putOrderedInt(this, STATE, EXCEPTIONAL); // final state
            // 一些 結束工作
            finishCompletion();
        }
    }

簡單翻譯一下方法的注釋:

  • 除非這個Future已經設置過了,或者被取消了,否則這個產生的異常將會匯報到ExecutionException里面
  • 如果在run()方法里面產生了異常,則會調用這個方法

所以總結一下就是:當任務執行過程中出現異常時候,對異常的處理方式

PS:這個方法是protected,所以可以重寫

1.3、set(V v)方法

這個方法主要是:執行結果的賦值操作

    /**
     * Sets the result of this future to the given value unless
     * this future has already been set or has been cancelled.
     *
     * <p>This method is invoked internally by the {@link #run} method
     * upon successful completion of the computation.
     *
     * @param v the value
     */
    protected void set(V v) {
        // state 狀態  NEW->COMPLETING
        if (U.compareAndSwapInt(this, STATE, NEW, COMPLETING)) {
            outcome = v;
            // COMPLETING -> NORMAL 到達穩定狀態
            U.putOrderedInt(this, STATE, NORMAL); // final state
            // 一些結束工作
            finishCompletion();
        }
    }

通過上面我們知道這個方法內部的流程如下:

  • 首先 將任務的狀態改變
  • 其次 將結果賦值
  • 再次 改變任務狀態
  • 最后 處理等待線程隊列(將線程阻塞狀態改為喚醒,這樣等待線程就拿到結果了)

PS:這里使用的是 UNSAFE的putOrderedInt方法,其實就是原子量的LazySet內部使用的方法,為什么要用這個方法?首先LazySet相對于Volatile-Write來說更加廉價,因為它沒有昂貴的Store/Load屏障,其次后續線程不會及時的看到state從COMPLETING變為NORMAL,但這沒有什么關系,而且NORMAL是state最終的狀態,不會再變化了。

在這個方法里面調用了finishCompletion()方法,那我們就來看下這個方法

1.4、finishCompletion()方法

這個方法是:

在任務執行完成(包括取消、正常結束、發生異常),將等待線程隊列喚醒,同時讓任務執行體清空。

代碼如下:

    /**
     * Removes and signals all waiting threads, invokes done(), and
     * nulls out callable.
     */
    private void finishCompletion() {
        // assert state > COMPLETING;
        // 遍歷等待節點
        for (WaitNode q; (q = waiters) != null;) {
            if (U.compareAndSwapObject(this, WAITERS, 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()方法)
        done();
        // 清空callable
        callable = null;        // to reduce footprint
    }

由代碼和注釋可以看出來,這里就是遍歷WaitNode鏈表,對每一個WaitNode對應的線程依次進行LockSupport.unpark(t),使其結束阻塞。WaitNode通知完畢后,調用done方法。目前該方法是空實現,所以如果你想在任務完成后執行一些業務邏輯可以重寫這個方法。所以這個方法主要是在于喚醒等待線程。由前面知道,當任務正常結束或者異常結束時,都會調用finishCompletion()去喚醒等待線程。這時候等待線程就可以醒來,可以獲取結果了。

·

1.4.1、LockSupport簡介

這里首先說下LockSupport,很多新手對這個東西,不是很熟悉,我先簡單說下,這里就不詳細說明了。

LockSupport是構建concurrent包的基礎之一

####### ① 操作對象
LockSupport調用Unsafe的natvie代碼:

public native void unpark(Thread jthread); 
public native void park(boolean isAbsolute, long time); 

這兩個函數聲明清楚地說明了操作對象:park函數是將當前Thread阻塞,而unPark函數則是將另一個Thread喚醒。

與Object類的wait/notify 機制相比,park/unpark有兩個優點:

  • 1、以thread為操作對象更符合阻塞線程的直觀定義
  • 2、操作更精準,可以準確地喚醒某一個線程(notify隨機喚醒一個線程,notifyAll喚醒所有等待的線程),增加了靈活性

####### ② 關于許可
在上面的文件,使用了阻塞和喚醒,是為了和wait/notify做對比。其實park/unpark的設計原理核心是"許可"。park是等待一個許可。unpark是為某線程提供一個"許可"。如果說某線程A調用park,那么除非另外一個線程unpark(A)給A一個許可,否則線程A將阻塞在park操作上。

1.5、handlePossibleCancellationInterrupt(int) 方法
    /**
     * Ensures that any interrupt from a possible cancel(true) is only
     * delivered to a task while in run or runAndReset.
     */
    private void handlePossibleCancellationInterrupt(int s) {
        // It is possible for our interrupter to stall before getting a
        // chance to interrupt us.  Let's spin-wait patiently.
        // 如果當前正在中斷過程中,自等待,等中斷完成
        if (s == INTERRUPTING)
            while (state == INTERRUPTING)
                Thread.yield(); // wait out pending interrupt

        // assert state == INTERRUPTED;

        // We want to clear any interrupt we may have received from
        // cancel(true).  However, it is permissible to use interrupts
        // as an independent mechanism for a task to communicate with
        // its caller, and there is no way to clear only the
        // cancellation interrupt.
        //
        // Thread.interrupted();
    }

先來看下注釋:

執行計算而不設置其結果,然后重新設置future為初始化狀態,如果執行遇到異常或者任務被取消,則不再執行此操作。這樣設計的目的是:執行多次任務。

看代碼我們知道他主要就是: 如果其他線程正在終止該任務,那么運行該任務的線程就暫時讓出CPU時間一直到state= INTERRUPTED為止。

所以它的作用就是:

確保cancel(true) 產生的中斷發生在run()或者 runAndReset()方法過程中

2、get()與get(long, TimeUnit)方法

任務是由線程池提供的線程執行,那么這時候主線程則會阻塞,直到任務線程喚醒它們。我們看看get()是怎么做的?

2.1 get()方法

代碼如下:

    /**
     * @throws CancellationException {@inheritDoc}
     */
    public V get() throws InterruptedException, ExecutionException {
        int s = state;
        // state 小于 COMPLETING 則說明任務仍然在執行,且沒有被取消
        if (s <= COMPLETING)
            s = awaitDone(false, 0L);
        return report(s);
    }

通過代碼我們知道

  • 首先 校驗參數
  • 然后 判斷是否正常執行且沒有被取消,如果沒有則調用awaitDone(boolean,long)方法
  • 最后 調用report(int) 方法

這里面涉及兩個方法分別是awaitDone(boolean,long)方法和report(int) 方法,那讓我們依次來看下。

2.1.1 awaitDone(boolean,long)方法

這個方法主要是等待任務執行完畢,如果任務取消或者超時則停止
代碼如下:


    /**
     * Awaits completion or aborts on interrupt or timeout.
     *
     * @param timed true if use timed waits
     * @param nanos time to wait, if timed
     * @return state upon completion or at timeout
     */
    private int awaitDone(boolean timed, long nanos)
        throws InterruptedException {
        // The code below is very delicate, to achieve these goals:
        // - call nanoTime exactly once for each call to park
        // - if nanos <= 0L, return promptly without allocation or nanoTime
        // - if nanos == Long.MIN_VALUE, don't underflow
        // - if nanos == Long.MAX_VALUE, and nanoTime is non-monotonic
        //   and we suffer a spurious wakeup, we will do no worse than
        //   to park-spin for a while
        // 起始時間
        long startTime = 0L;    // Special value 0L means not yet parked
        // 當前等待線程的節點
        WaitNode q = null;
         // 是否將節點放在了等待列表中
        boolean queued = false;
        // 通過死循環來實現線程阻塞等待
        for (;;) {
            int s = state;
            if (s > COMPLETING) {
                 // 任務可能已經完成或者被取消了
                if (q != null)
                    q.thread = null;
                return s;
            }
            else if (s == COMPLETING)
                // We may have already promised (via isDone) that we are done
                // so never return empty-handed or throw InterruptedException
                // 任務線程可能被阻塞了,讓出cpu
                Thread.yield();
            else if (Thread.interrupted()) {
                // 線程中斷則移除等待線程并拋出異常
                removeWaiter(q);
                throw new InterruptedException();
            }
            else if (q == null) {
                // 等待節點為空,則初始化新節點并關聯當前線程
                if (timed && nanos <= 0L)
                   // 如果需要等待,并且等待時間小于0表示立即,則直接返回
                    return s;
                // 如果不需要等待,或者需要等待但是等待時間大于0。
                q = new WaitNode();
            }
            else if (!queued)
                // 等待線程入隊,因為如果入隊成功則queued=true
                queued = U.compareAndSwapObject(this, WAITERS,
                                                q.next = waiters, q);
            else if (timed) {
                //如果有超時設置
                final long parkNanos;
                if (startTime == 0L) { // first time
                    startTime = System.nanoTime();
                    if (startTime == 0L)
                        startTime = 1L;
                    parkNanos = nanos;
                } else {
                    long elapsed = System.nanoTime() - startTime;
                    if (elapsed >= nanos) {
                        // 已經超時,則移除等待節點
                        removeWaiter(q);
                        return state;
                    }
                    parkNanos = nanos - elapsed;
                }
                // nanoTime may be slow; recheck before parking
                if (state < COMPLETING)
                    // 任務還在執行,且沒有被取消,所以繼續等待
                    LockSupport.parkNanos(this, parkNanos);
            }
            else
                LockSupport.park(this);
        }
    }

兩個入參:

  • timed 為true 表示設置超時時間,false表示不設置超時間
  • nanos 表示超時的狀態

for死循環里面的邏輯如下:

  • 第一步 判斷任務是否已經完處于完成或者取消了,如果直接返回轉狀態值,如果不是,則走第二步
  • 第二步,如果狀態值是COMPLETING,則說明當前是在set()方法時被阻塞了,所以只需要讓出當前線程的CPU資源。
  • 第三步,如果線程已經中斷了,則移除線程并拋出異常
  • 第四步,如果能走到這一步,狀態值只剩下NEW了,如果狀態值是NEW,并且q==null,則說明這是第一次,所以初始化一個當前線程的等待節點。
  • 第五步,此時queued=false,說明如果還沒入隊,則它是在等待入隊
  • 第六步,能走到這一步,說明queued=true,這時候判斷timed是否為true,如果為true則設置了超時時間,然后看一下startTime是否為0,如果為0,則說明是第一次,因為startTime默認值為0,如果是第一此,則設置startTime=1。保證startTime==0是第一次。如果startTime!=0,則說明不是第一次,如果不是第一次,則需要計算時差elapsed,如果elapsed大于nanos,則說明超時,如果小于則沒有超時,還有時差,然等待這個時差阻塞。
  • 第七步,如果timed為false,則說明沒有超時設置。

所以總結一下:

waitDone就是將當前線程加入等待隊列(waitNode有當前Thread的Thread變量),然后用LockSupport將自己阻塞,等待超時或者被解除阻塞后,判斷是否已經完成(state為>= COMPLETING),如果未完成(state< COMPLETING)拋出超時異常,如果已完成則稍等或者直接返回結果。

這個方法里面調用了removeWaiter(WaitNode) 這個方法,所以我們來先看這個這個removeWaiter(WaitNode) 里面是怎么實現的

2.1.2 removeWaiter(WaitNode)
    /**
     * Tries to unlink a timed-out or interrupted wait node to avoid
     * accumulating garbage.  Internal nodes are simply unspliced
     * without CAS since it is harmless if they are traversed anyway
     * by releasers.  To avoid effects of unsplicing from already
     * removed nodes, the list is retraversed in case of an apparent
     * race.  This is slow when there are a lot of nodes, but we don't
     * expect lists to be long enough to outweigh higher-overhead
     * schemes.
     */
    private void removeWaiter(WaitNode node) {
        if (node != null) {
            // 將node 的thread 域置空
            node.thread = 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 (!U.compareAndSwapObject(this, WAITERS, q, s))
                        continue retry;
                }
                break;
            }
        }
    }

首先來看下類的注釋

為了防止累積的內存垃圾,所以需要去取消超時或者已經被中斷的等待節點。內部節點因為沒有CAS所以很簡單,所以他們可以被無害的釋放。為了避免已刪除節點的影響,如果存在競爭的情況下,需要重新排列。所以當節點很多是,速度會很慢,因此我們不建議列表太長而導致效率降低。

這個方法主要就是將線程節點從等待隊列中移除

2.1.2 report(int) 方法
    /**
     * Returns result or throws exception for completed task.
     *
     * @param s completed state value
     */
    @SuppressWarnings("unchecked")
    private V report(int s) throws ExecutionException {
        Object x = outcome;
         // 如果任務正常執行完成,返回任務執行結果
        if (s == NORMAL)
            return (V)x;

        // 如果任務被取消,拋出異常
        if (s >= CANCELLED)
            throw new CancellationException();
        
        // 其他狀態 拋出執行異常 ExecutionException 
        throw new ExecutionException((Throwable)x);
    }
report.png

如果任務處于NEW、COMPLETING和INTERRUPTING 這三種狀態的時候是執行不到report方法的,所以沒有對這三種狀態盡心轉換。

2.2 get(long, TimeUnit)方法

最多等待為計算完成所給的時間之后,獲取其結果

    /**
     * @throws CancellationException {@inheritDoc}
     */
    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方法源碼很簡潔,首先校驗參數,然后根據state狀態判斷是否超時,如果超時則異常,不超時則調用report去獲取最終結果。
當 s <= COMPLETING 時,表明任務仍然在執行且沒有被取消,如果它為true,那么走到awaitDone方法。關于awaitDone方法上面已經講解了,這里就不過闡述了。

3、cancel(boolean)方法

只能取消還沒有被執行的任務(任務狀態為NEW的任務)

    public boolean cancel(boolean mayInterruptIfRunning) {
        // 如果任務狀態不是初始化狀態,則取消任務
         //如果此時任務已經執行了,并且可能執行完成,但是狀態改變還沒有來得及修改,也就是在run()方法中的set()方法還沒來得及調用
         //   繼續判斷任務的當前狀態時否為NEW,因為此時執行任務線程可能再度獲得處理了,任務狀態可能已發生改變
        if (!(state == NEW &&
              U.compareAndSwapInt(this, STATE, NEW,
                  mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
                return false;

        // 如果任務狀態依然是NEW,也就是執行線程沒有改變任務的狀態,
        // 則讓執行線程中斷(在這個過程中執行線程可能會改變任務的狀態)
        try {    // in case call to interrupt throws exception
            if (mayInterruptIfRunning) {
                try {
                    Thread t = runner;
                    if (t != null)
                        t.interrupt();
                } finally { // final state
                    // 將任務狀態設置為中斷
                    U.putOrderedInt(this, STATE, INTERRUPTED);
                }
            }
        } finally {
            // 處理任務完成的結果
            finishCompletion();
        }
        return true;
    }

通過上述代碼,我們知道這個取消不一定起作用的。

上面的代碼邏輯如下:

  • 第一步:state不等于NEW,則表示任務即將進入最終狀態 ,則state == NEW為false,導致if成立直接返回false
  • 第二步:如果mayInterruptIfRunning為true在,表示中斷線程,則設置狀態為INTERRUPTING,中斷之后設置為INTERRUPTED。如果mayInterruptIfRunning為false,表示不中斷線程,把state設置為CANCELLED
  • 第三步:state狀態為NEW,任務可能已經開始執行,也可能還未開始,所以用Unsafe查看下,如果不是,則直接返回false
  • 第四步:移除等待線程
  • 第五步:喚醒

所以,cancel()方法改變了futureTask的狀態為,如果傳入的是false,并且業務邏輯已經開始執行,當前任務是不會被終止的,而是會繼續執行,知道異常或者執行完畢。如果傳入的是true,會調用當前線程的interrupt()方法,把中斷標志位設為true。

事實上,除非線程自己停止自己的任務,或者退出JVM,是沒有其他方法完全終止一個線程任務的。mayInterruptIfRunning=true,通過希望當前線程可以響應中斷的方式來結束任務。當任務被取消后,會被封裝為CancellationException拋出。

4、runAndReset() 方法

任務可以被多次執行

    /**
     * Executes the computation without setting its result, and then
     * resets this future to initial state, failing to do so if the
     * computation encounters an exception or is cancelled.  This is
     * designed for use with tasks that intrinsically execute more
     * than once.
     *
     * @return {@code true} if successfully run and reset
     */
    protected boolean runAndReset() {
        if (state != NEW ||
            !U.compareAndSwapObject(this, RUNNER, 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;
    }

先來看下注釋:

執行計算而不設置其結果,然后重新設置future為初始化狀態,如果執行遇到異常或者任務被取消,則不再執行此操作。這樣設計的目的是:執行多次任務。

我們可以對比一下runAndReset與run方法,其實兩者相差不大,主要就是有兩點區別

  • 1 run()方法里面設置了result的值,而runAndReset()則移除了這段代碼

下面我們就來看下handlePossibleCancellationInterrupt(int) 這個方法

五、總結

FutureTask大部分就簡單分析完了,其他的自己看下就行了。FutureTask中的任務狀態由變量state表示,任務狀態都是基于state判斷。而FutureTask的阻塞則是通過自旋+掛起線程實現的。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 227,702評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,143評論 3 415
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 175,553評論 0 373
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,620評論 1 307
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,416評論 6 405
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 54,940評論 1 321
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,024評論 3 440
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,170評論 0 287
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,709評論 1 333
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,597評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,784評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,291評論 5 357
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,029評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,407評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,663評論 1 280
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,403評論 3 390
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,746評論 2 370

推薦閱讀更多精彩內容