FutureTask源碼解析(1)——預備知識

前言

系列文章目錄 java

FutureTask 是一個同步工具類,它實現了Future語義,表示了一種抽象的可生成結果的計算。在包括線程池在內的許多工具類中都會用到,弄懂它的實現將有利於咱們更加深刻地理解Java異步操做實現。編程

在分析它的源碼以前, 咱們須要先了解一些預備知識。本篇咱們先來看看FutureTask 中所使用到的接口:RunnableCallableFutureRunnableFuture以及所使用到的工具類ExecutorsUnsafesegmentfault

FutureTask所使用到的接口

Runnable接口

在前面Thread類源碼解讀的系列文章中咱們說過, 建立線程最重要的是傳遞一個run()方法, 這個run方法定義了這個線程要作什麼事情, 它被抽象成了Runnable接口:設計模式

@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

可是, 能夠發現, 這個方法並無任何返回值.
若是咱們但願執行某種類型的操做並拿到它的執行結果, 該怎麼辦呢?多線程

從 Runnable 到 Callable

要從某種類型的操做中拿到執行結果, 最簡單的方式天然是令這個操做本身返回操做結果, 則相較於run方法返回void,咱們能夠令一個操做返回特定類型的對象, 這種思路的實現就是Callable接口:併發

@FunctionalInterface
public interface Callable<V> {
    V call() throws Exception;
}

對比Callable接口與Runnable接口, 咱們能夠發現它們最大的不一樣點在於:app

  1. Callable有返回值
  2. Callable能夠拋出異常

關於有返回值這點,咱們並不意外,由於這就是咱們的需求,call方法的返回值類型採用的泛型,該類型是咱們在建立Callable對象的時候指定的。less

除了有返回值外,相較於Runnable接口,Callable還能夠拋出異常,這點看上去好像沒啥特別的,可是卻有大用處——這意味着若是在任務執行過程當中發生了異常,咱們能夠將它向上拋出給任務的調用者來妥善處理,咱們甚至能夠利用這個特性來中斷一個任務的執行。而Runnable接口的run方法不能拋出異常,只能在方法內部catch住處理,喪失了必定的靈活性。異步

使用Callable接口解決了返回執行結果的問題, 可是也帶來了一個新的問題:工具

如何得到執行結果?

有的同窗可能就要說了, 這還不簡單? 直接拿不就行了, 看個人:

public static void main(String[] args) {
    Callable<String> myCallable = () -> "This is the results.";
    try {
        String result = myCallable.call();
        System.out.println("Callable 執行的結果是: " + result);
    } catch (Exception e) {
        System.out.println("There is a exception.");
    }
}

這種方法確實能夠, 可是它存在幾個問題:

  1. call方法是在當前線程中直接調用的, 沒法利用多線程。
  2. call方法多是一個特別耗時的操做, 這將致使程序停在myCallable.call()調用處, 沒法繼續運行, 直到call方法返回。
  3. 若是call方法始終不返回, 咱們沒辦法中斷它的運行。

所以, 理想的操做應當是, 咱們將call方法提交給另一個線程執行, 並在合適的時候, 判斷任務是否完成, 而後獲取線程的執行結果或者撤銷任務, 這種思路的實現就是Future接口:

Future接口

Future接口被設計用來表明一個異步操做的執行結果。你能夠用它來獲取一個操做的執行結果、取消一個操做、判斷一個操做是否已經完成或者是否被取消

public interface Future<V> {
    V get() throws InterruptedException, ExecutionException;
    V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException;
    
    boolean cancel(boolean mayInterruptIfRunning);
    boolean isCancelled();
    
    boolean isDone();
}

Future接口一共定義了5個方法:

  • get()

    • 該方法用來獲取執行結果, 若是任務還在執行中, 就阻塞等待;
  • get(long timeout, TimeUnit unit)

    • 該方法同get方法相似, 所不一樣的是, 它最多等待指定的時間, 若是指定時間內任務沒有完成, 則會拋出TimeoutException異常;
  • cancel(boolean mayInterruptIfRunning)

    • 該方法用來嘗試取消一個任務的執行, 它的返回值是boolean類型, 表示取消操做是否成功.
  • isCancelled()

    • 該方法用於判斷任務是否被取消了。若是一個任務在正常執行完成以前被cancel掉了, 則返回true
  • isDone()

    • 若是一個任務已經結束, 則返回true。注意, 這裏的任務結束包含了如下三種狀況:

      • 任務正常執行完畢
      • 任務拋出了異常
      • 任務已經被取消

關於cancel方法,這裏要補充說幾點:
首先有如下三種狀況之一的,cancel操做必定是失敗的:

  1. 任務已經執行完成了
  2. 任務已經被取消過了
  3. 任務由於某種緣由不能被取消

其它狀況下,cancel操做將返回true。值得注意的是,cancel操做返回true並不表明任務真的就是被取消了,這取決於發動cancel狀態時任務所處的狀態:

  1. 若是發起cancel時任務尚未開始運行,則隨後任務就不會被執行;
  2. 若是發起cancel時任務已經在運行了,則這時就須要看mayInterruptIfRunning參數了:

    • 若是mayInterruptIfRunning 爲true, 則當前在執行的任務會被中斷
    • 若是mayInterruptIfRunning 爲false, 則能夠容許正在執行的任務繼續運行,直到它執行完

這個cancel方法的規範看起來有點繞,如今不太理解不要緊,後面結合實例去看就容易弄明白了,咱們將在下一篇分析FutureTask源碼的時候詳細說說FutureTask對這一方法的實現。

RunnableFuture 接口

RunnableFuture接口人如其名, 就是同時實現了Runnable接口和Future接口:

public interface RunnableFuture<V> extends Runnable, Future<V> {
    void run(); 
}

咱們下一篇開始分析FutureTask的源碼的時候就將看到,FutureTask實現了該接口,也就是至關於它同時實現了Runnable接口和Future接口。

有的同窗可能會對這個接口產生疑惑,既然已經繼承了Runnable,該接口天然就繼承了run方法,爲何要在該接口的內部再寫一個run方法?

單純從理論上來講,這裏確實是沒有必要的,再多寫一遍,我以爲大概就是爲了看上去直觀一點,便於文檔或者UML圖展現。

FutureTask所使用到的工具類

Executors

Executors 是一個用於建立線程池的工廠類,關於線程池的概念,咱們之後再說。這個類同時也提供了一些有用的靜態方法。

前面咱們提到了Callable接口,它是JDK1.5才引入的,而Runnable接口在JDK1.0就有了,咱們有時候須要將一個已經存在Runnable對象轉換成Callable對象,Executors工具類爲咱們提供了這一實現:

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

能夠明顯看出來,這個方法採用了設計模式中的適配器模式,將一個Runnable類型對象適配成Callable類型。

由於Runnable接口沒有返回值, 因此爲了與Callable兼容, 咱們額外傳入了一個result參數, 使得返回的Callable對象的call方法直接執行Runnable的run方法, 而後返回傳入的result參數。

有的同窗要說了, 你把result參數傳進去, 又原封不動的返回出來, 有什麼意義呀?
這樣作確實沒什麼意義, result參數的存在只是爲了將一個Runnable類型適配成Callable類型.

Unsafe

Unsafe類對於併發編程來講是個很重要的類,若是你稍微看過J.U.C裏的源碼(例如咱們前面講AQS系列的文章裏),你會發現處處充斥着這個類的方法調用。

這個類的最大的特色在於,它提供了硬件級別的CAS原子操做。

可能有的同窗會以爲這並無什麼了不得,CAS的概念都被說爛了。可是,CAS能夠說是實現了最輕量級的鎖,當多個線程嘗試使用CAS同時更新同一個變量時,只有其中的一個線程能成功地更新變量的值,而其餘的線程將失敗。然而,失敗的線程並不會被掛起。

CAS操做包含了三個操做數: 須要讀寫的內存位置,進行比較的原值,擬寫入的新值。

在Unsafe類中,實現CAS操做的方法是: compareAndSwapXXX

例如:

public native boolean compareAndSwapObject(Object obj, long offset, Object expect, Object update);
  • obj是咱們要操做的目標對象
  • offset表示了目標對象中,對應的屬性的內存偏移量
  • expect是進行比較的原值
  • update是擬寫入的新值。

因此該方法實現了對目標對象obj中的某個成員變量(field)進行CAS操做的功能。

那麼,要怎麼得到目標field的內存偏移量offset呢? Unsafe類爲咱們提供了一個方法:

public native long objectFieldOffset(Field field);

該方法的參數是咱們要進行CAS操做的field對象,要怎麼得到這個field對象呢?最直接的辦法就是經過反射了:

Class<?> k = FutureTask.class;
Field stateField = k.getDeclaredField("state");

這樣一波下來,咱們就能對FutureTask的state屬性進行CAS操做了o( ̄▽ ̄)o

除了compareAndSwapObject,Unsafe類還提供了更爲具體的對int和long類型的CAS操做:

public native boolean compareAndSwapInt(Object obj, long offset, int expect, int update);
public native boolean compareAndSwapLong(Object obj, long offset, long expect, long update);

從方法簽名能夠看出,這裏只是把目標field的類型限定成int和long類型,而不是通用的Object.

最後,FutureTask還用到了一個方法:

public native void putOrderedInt(Object obj, long offset, int value);

能夠看出,該方法只有三個參數,因此它沒有比較再交換的概念,某種程度上就是一個賦值操做,即設置obj對象中offset偏移地址對應的int類型的field的值爲指定值。這實際上是Unsafe的另外一個方法putIntVolatile的有序或者有延遲的版本,而且不保證值的改變被其餘線程當即看到,只有在field被volatile修飾而且指望被意外修改的時候使用纔有用。

那麼putIntVolatile方法的定義是什麼呢?

public native void putIntVolatile(Object obj, long offset, int value);

該方法設置obj對象中offset偏移地址對應的整型field的值爲指定值,支持volatile store語義。由此能夠看出,當操做的int類型field自己已經被volatile修飾時,putOrderedIntputIntVolatile是等價的。

好了,到這裏,基本須要用到的預備知識咱們都學習完了,障礙已經掃清,下一篇咱們就能夠愉快地看FutureTask的源碼了(๑¯∀¯๑)

(完)

查看更多系列文章: 系列文章目錄

相關文章
相關標籤/搜索