天天看點

Java異步程式設計實戰:基于JDK中的Future實作異步程式設計

作者:技術聯盟總壇

一、前言

本節主要講解如何使用JDK中的Future實作異步程式設計,這包含如何使用FutureTask實作異步程式設計以及其内部實作原理以及FutureTask的局限性。

二、 JDK 中的Future

在Java并發包(JUC包)中Future代表着異步計算結果,Future中提供了一些列方法用來檢查計算結果是否已經完成,還提供了同步等待任務執行完成的方法,以及擷取計算結果的方法等。當計算結果完成時隻能通過提供的get系列方法來擷取結果,如果使用了不帶逾時時間的get方法則在計算結果完成前,調用線程會被一直阻塞。另外計算任務是可以使用cancle方法來取消的,但是一旦一個任務計算完成,則不能再被取消了。

首先我們看下Future接口的類圖結構如圖3-1-1:

Java異步程式設計實戰:基于JDK中的Future實作異步程式設計

圖3-1-1Future類圖

如上圖3-1-1Future類總共就有5個接口方法,下面我們來一一講解:

  • V get() throws InterruptedException, ExecutionException :等待異步計算任務完成,并傳回結果;如果目前任務計算還沒完成則會阻塞調用線程直到任務完成;如果在等待結果的過程中有其他線程取消了該任務,則調用線程抛出CancellationException異常;如果在等待結果的過程中有其他線程中斷了該線程,則調用線程抛出InterruptedException異常;如果任務計算過程中抛出了異常,則調用線程會抛出ExecutionException異常。
  • V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException:相比get()方法多了逾時時間,不同在于線程調用了該方法後在任務結果沒有計算出來前調用線程不會一直被阻塞,而是會在等待timeout個unit機關的時間後抛出TimeoutException異常後傳回。添加逾時時間這避免了調用線程死等的情況,讓調用線程可以及時釋放。
  • boolean isDone():如果計算任務已經完成則傳回true,否則傳回false,需要注意的是任務完成是指任務正常完成了或者由于抛出異常而完成了或者任務被取消了。
  • boolean cancel(boolean mayInterruptIfRunning) :嘗試取消任務的執行;如果目前任務已經完成或者任務已經被取消了,則嘗試取消任務會失敗;如果任務還沒被執行時候,調用了取消任務,則任務将永遠不會被執行;如果任務已經開始運作了,這時候取消任務,則參數mayInterruptIfRunning将決定是否要将正在執行任務的線程中斷,如果為true則辨別要中斷,否則辨別不中斷;當調用取消任務後,在調用isDone()方法,後者會傳回true,随後調用isCancelled()方法也會一直傳回true;該方法會傳回false,如果任務不能被取消,比如任務已經完成了,任務已經被取消了。
  • boolean isCancelled():如果任務在被執行完畢前被取消了,則該方法傳回true,否則傳回false。

三 JDK中的FutureTask

3.1 FutureTask 概述

FutureTask代表了一個可被取消的異步計算任務,該類實作了Future接口,比如提供了啟動和取消任務、查詢任務是否完成、擷取計算結果的接口。

FutureTask任務的結果隻有當任務完成後才能擷取,并且隻能通過get系列方法擷取,當結果還沒出來時候,線程調用get系列方法會被阻塞;另外一旦任務被執行完成,任務不能被重新開機,除非運作時候使用了runAndReset方法;FutureTask中的任務可以是Callable類型,也可以是Runnable類型(因為FutureTask實作了Runnable接口),FutureTask類型的任務可以被送出到線程池執行。

我們修改上節的例子如下:

public class AsyncFutureExample {

    public static String doSomethingA() {

        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("--- doSomethingA---");

        return "TaskAResult";
    }

    public static String doSomethingB() {
        try {
            Thread.sleep(2000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("--- doSomethingB---");
        return "TaskBResult";

    }

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        long start = System.currentTimeMillis();

        // 1.建立future任務
        FutureTask<String> futureTask = new FutureTask<String>(() -> {
            String result = null;
            try {
                result = doSomethingA();

            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        });

        // 2.開啟異步單元執行任務A
        Thread thread = new Thread(futureTask, "threadA");
        thread.start();

        // 3.執行任務B
        String taskBResult = doSomethingB();

        // 4.同步等待線程A運作結束
        String taskAResult = futureTask.get();

        //5.列印兩個任務執行結果
        System.out.println(taskAResult + " " + taskBResult); 
        System.out.println(System.currentTimeMillis() - start);

    }
}           
  • 如上代碼doSomethingA和doSomethingB方法都是有傳回值的任務,main函數内代碼1建立了一個異步任務futureTask,其内部執行任務doSomethingA。
  • 代碼2則建立了一個線程,并且以futureTask為執行任務,并且啟動;代碼3使用main線程執行任務doSomethingB,這時候任務doSomethingB和doSomethingA是并發運作的,等main函數運作doSomethingB完畢後,執行代碼4同步等待doSomethingA任務完成,然後代碼5列印兩個任務的執行結果。
  • 如上可知使用FutureTask可以擷取到異步任務的結果。

當然我們也可以把FutureTask送出到線程池來執行,使用線程池運作方式代碼如下:

// 0自定義線程池
    private final static int AVALIABLE_PROCESSORS = Runtime.getRuntime().availableProcessors();
    private final static ThreadPoolExecutor POOL_EXECUTOR = new ThreadPoolExecutor(AVALIABLE_PROCESSORS,
            AVALIABLE_PROCESSORS * 2, 1, TimeUnit.MINUTES, new LinkedBlockingQueue<>(5),
            new ThreadPoolExecutor.CallerRunsPolicy());

    public static void main(String[] args) throws InterruptedException, ExecutionException {

        long start = System.currentTimeMillis();

        // 1.建立future任務
        FutureTask<String> futureTask = new FutureTask<String>(() -> {
            String result = null;
            try {
                result = doSomethingA();

            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        });

        // 2.開啟異步單元執行任務A
        POOL_EXECUTOR.execute(futureTask);

        // 3.執行任務B
        String taskBResult = doSomethingB();

        // 4.同步等待線程A運作結束
        String taskAResult = futureTask.get();
        // 5.列印兩個任務執行結果
        System.out.println(taskAResult + " " + taskBResult);
        System.out.println(System.currentTimeMillis() - start);
    }           

如上可知代碼0建立了一個線程池,代碼2添加異步任務到線程池,這裡我們是調用了線程池的execute方法把futureTask送出到線程池的,其實下面代碼與上面是等價的:

public static void main(String[] args) throws InterruptedException, ExecutionException {

        long start = System.currentTimeMillis();

        // 1.開啟異步單元執行任務A
        Future<String> futureTask = POOL_EXECUTOR.submit(() -> {
            String result = null;
            try {
                result = doSomethingA();

            } catch (Exception e) {
                e.printStackTrace();
            }
            return result;
        });

        // 2.執行任務B
        String taskBResult = doSomethingB();

        // 3.同步等待線程A運作結束
        String taskAResult = futureTask.get();
        // 4.列印兩個任務執行結果
        System.out.println(taskAResult + " " + taskBResult);
        System.out.println(System.currentTimeMillis() - start);
    }           

如上代碼1我們調用了線程池的submit方法送出了一個任務到線程池,然後傳回了一個FutureTask對象。

3.2 FutureTask的類圖結構:

由于FutureTask在異步程式設計領域還是比較重要的,是以我們有必要探究下其原理,以便加深對異步的了解,首先我們來看下其類圖結構如圖3-2-2-1:

Java異步程式設計實戰:基于JDK中的Future實作異步程式設計

圖3-2-2-1 FutureTask的類圖

  • 如上時序圖3-2-2-1FutureTask實作了Future接口的所有方法,并且實作了Runnable接口,是以其是可執行任務,可以投遞到線程池或者線程來執行。
  • FutureTask中變量state是一個使用volatile關鍵字修飾(用來解決多線程下記憶體不可見問題,具體可以參考《Java并發程式設計之美》一書)的int類型,用來記錄任務狀态,任務狀态枚舉值如下:
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;當通過set、 setException、 cancel函數設定任務結果時候,任務會轉換為終止狀态;在任務完成過程中,設定任務狀态會變為COMPLETING(當結果被使用set方法設定時候),也可能會經過INTERRUPTING狀态(當使用cancel(true)方法取消任務并中斷任務時候);當任務被中斷後,任務狀态為INTERRUPTED;當任務被取消後,任務狀态為CANCELLED;當任務正常終止時候,任務狀态為NORMAL;當任務執行異常後,任務會變為EXCEPTIONAL狀态。

另外在任務運作過程中,任務可能的狀态轉換路徑如下:

  • NEW -> COMPLETING -> NORMAL :正常終止流程轉換
  • NEW -> COMPLETING -> EXCEPTIONAL:執行過程中發生異常流程轉換
  • NEW -> CANCELLED:任務還沒開始就被取消
  • NEW -> INTERRUPTING -> INTERRUPTED:任務被中斷

從上述轉換可知,任務最終隻有四種終态:NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTED,另外可知任務的狀态值是從上到下遞增的。

  • 類圖中callable是有傳回值的可執行任務,建立FutureTask對象時候,可以通過構造函數傳遞該任務。
  • 類圖中outcome是任務運作的結果,可以通過get系列方法來擷取該結果,另外outcome這裡沒有被修飾為volatile,是因為變量state已經被使用volatile修飾了,這裡是借用volatile的記憶體語義來保證寫入outcome時候會把值重新整理到主記憶體,讀取時候會從主記憶體讀取,進而避免多線程下記憶體不可見問題(可以參考《Java并發程式設計之美》一書)。
  • 類圖中runner變量,記錄了運作該任務的線程,這個是在FutureTask的run方法内使用CAS函數設定的。
  • 類圖中waiters變量是一個WaitNode節點,使用Treiber stack實作的無鎖棧,棧頂元素就是使用waiters代表,棧用來記錄所有等待任務結果的線程節點,其定義為:
static final class WaitNode {
        volatile Thread thread;
        volatile WaitNode next;
        WaitNode() { thread = Thread.currentThread(); }
    }           

可知其是一個簡單的連結清單,用來記錄所有等待結果而被阻塞的線程。

最後我們看下其構造函數:

public FutureTask(Callable<V> callable) {
        if (callable == null)
            throw new NullPointerException();
        this.callable = callable;
        this.state = NEW;       
    }           

如上代碼可知構造函數内儲存了傳遞的callable任務到callable變量,并且設定任務狀态為NEW,這裡由于state為volatile修飾,是以寫入state的值可以保證callable的寫入也會被刷入主記憶體,這避免了多線程下記憶體不可見性。

另外還有一個構造函數:

public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;     
    }           

該函數傳入一個Runnable類型的任務,由于該任務是不具有傳回值的,是以這裡使用Executors.callable方法進行适配,适配為Callable類型的任務.

Executors.callable(runnable, result);把Runnable類型任務轉換為了callable:

public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, 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;
        }
    }           

如上可知使用了擴充卡模式來做轉換。

另外FutureTask中使用了UNSAFE機制來操作記憶體變量:

private static final sun.misc.Unsafe UNSAFE;

    private static final long stateOffset;//state變量的偏移位址
    private static final long runnerOffset;//runner變量的偏移位址
    private static final long waitersOffset;//waiters變量的偏移位址
    static {
        try {
            //擷取UNSAFE的執行個體
            UNSAFE = sun.misc.Unsafe.getUnsafe();
            Class<?> k = FutureTask.class;
            //擷取變量state的偏移位址
            stateOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("state"));
            //擷取變量runner的偏移位址
            runnerOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("runner"));
            //擷取變量waiters變量的偏移位址
            waitersOffset = UNSAFE.objectFieldOffset
                (k.getDeclaredField("waiters"));
        } catch (Exception e) {
            throw new Error(e);
        }
}           

如上代碼分别擷取了FutureTask中幾個變量在FutureTask對象内的記憶體位址偏移量,以便實作中使用UNSAFE的CAS操作來操作這些變量。

3.3 FutureTask的run() 方法

該方法是任務的執行體,線程是調用該方法來具體運作任務的,如果任務沒有被取消,則該方法會運作任務,并且設定結果到outcome變量裡面,其代碼:

public void run() {
    //1.如果任務不是初始化的NEW狀态,或者使用CAS設定runner為目前線程失敗,則直接傳回
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    //2.如果任務不為null,并且任務狀态為NEW,則執行任務
    try {
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            //2.1執行任務,如果OK則設定ran标記為true
            try {
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
            //2.2執行任務出現異常,則标記false,并且設定異常
                result = null;
                ran = false;
                setException(ex);
            }
            //3.任務執行正常,則設定結果
            if (ran)
                set(result);
        }
    } finally {


        runner = null;
        int s = state;
       //4.為了保證調用cancel(true)的線程在該run方法傳回前中斷任務執行的線程
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

private void handlePossibleCancellationInterrupt(int s) {
    //為了保證調用cancel在該run方法傳回前中斷任務執行的線程
    //這裡使用Thread.yield()讓run方法執行線程讓出cpu執行權,以便讓
    //cancel(true)的線程執行cancel(true)中的代碼中斷任務線程
    if (s == INTERRUPTING)
        while (state == INTERRUPTING)
            Thread.yield(); // wait out pending interrupt
}           
  • 如上代碼1,如果任務不是初始化的NEW狀态,或者使用CAS設定runner為目前線程失敗,則直接傳回;這個可以防止同一個FutureTask對象被送出給多個線程來執行,導緻run方法被多個線程同時執行造成混亂。
  • 代碼2,如果任務不為null,并且任務狀态為NEW,則執行任務,其中代碼2.1調用c.call()具體執行任務,如果任務執行OK,則調用set方法把結果記錄到result,并設定ran為true;否則執行任務過程中抛出異常則設定result為null,ran為false,并且調用setException設定異常資訊後,任務就處于終止狀态,其中setException代碼如下:
protected void setException(Throwable t) {
    //2.2.1
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        //2.2.1.1
        finishCompletion();
    }
}           

如上代碼,使用CAS嘗試設定任務狀态state為COMPLETING,如果CAS成功則把異常資訊設定到outcome變量,并且設定任務狀态為EXCEPTIONAL終止狀态,然後調用finishCompletion,其代碼:

private void finishCompletion() {
    //a周遊連結清單節點
    for (WaitNode q; (q = waiters) != null;) {
        //a.1 CAS設定目前waiters節點為null
        if (UNSAFE.compareAndSwapObject(this, waitersOffset, q, null)) {
            //a.1.1
            for (;;) {
                //喚醒目前q節點對應的線程
                Thread t = q.thread;
                if (t != null) {
                    q.thread = null;
                    LockSupport.unpark(t);
                }
                //擷取q的下一個節點
                WaitNode next = q.next;
                if (next == null)
                    break;
                q.next = null; //help gc
                q = next;
            }
            break;
        }
    }
    //b。所有阻塞的線程都被喚醒後,調用done方法
    done();

    callable = null;        // callable設定為null
}           

如上代碼比較簡單,就是當任務已經處于終态後,激活waiters連結清單中所有由于等待擷取結果而被阻塞的線程,并從waiters連結清單中移除他們,等所有由于等待該任務結果的線程被喚醒後,調用done()方法,done預設實作為空實作。

上面我們講了當任務執行過程中出現異常後如何處理的,下面我們看代碼3,當任務是正常執行完畢後set(result)的實作:

protected void set(V v) {
    //3.1
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = v;
        UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
        finishCompletion();
    }
}           

如上代碼3.1,使用CAS嘗試設定任務狀态state為COMPLETING,如果CAS成功則把任務結果設定到outcome變量,并且設定任務狀态為NORMAL終止狀态,然後調用finishCompletion喚醒所有因為等待結果而被阻塞的線程。

3.4 FutureTask的get()方法

等待異步計算任務完成,并傳回結果;如果目前任務計算還沒完成則會阻塞調用線程直到任務完成;如果在等待結果的過程中有其他線程取消了該任務,則調用線程會抛出CancellationException異常;如果在等待結果的過程中有線程中斷了該線程,則抛出InterruptedException異常;如果任務計算過程中抛出了異常,則會抛出ExecutionException異常。

其代碼如下:

public V get() throws InterruptedException, ExecutionException {
    //1.擷取狀态,如有需要則等待
    int s = state;
    if (s <= COMPLETING)
        //等待任務終止
        s = awaitDone(false, 0L);
    //2.傳回結果
    return report(s);
}           
  • 如上代碼1擷取任務的狀态,如果任務狀态的值小于等于COMPLETING則說明任務還沒有被完成,是以調用awaitDone挂起調用線程。
  • 代碼2如果任務已經被完成,則傳回結果。下面我們看awaitDone方法實作:
private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
    //1.1逾時時間
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    //1.2 循環,等待任務完成
    for (;;) {
        //1.2.1任務被中斷,則移除等待線程節點,抛出異常
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }
        //1.2.2 任務狀态>COMPLETING說明任務已經終止
        int s = state;
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;   
        }
        //1.2.3任務狀态為COMPLETING
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
        //1.2.4為目前線程建立節點
        else if (q == null)
            q = new WaitNode();
        //1.2.5 添加目前線程節點到連結清單
        else if (!queued)
            queued = UNSAFE.compareAndSwapObject(this, waitersOffset,
                                               q.next = waiters, q);
        //1.2.6 設定了逾時時間
        else if (timed) {
            nanos = deadline - System.nanoTime();
            if (nanos <= 0L) {
                removeWaiter(q);
                return state;
            }
            LockSupport.parkNanos(this, nanos);
        }
        //1.2.7沒有設定逾時時間
        else
            LockSupport.park(this);
    }
}           
  • 如上代碼1.1擷取設定的逾時時間,如果傳遞的timed為false說明沒有設定逾時時間,則deadline設定為0
  • 代碼1.2無限循環等待任務完成,其中代碼1.2.1如果發現目前線程被中斷則從等待連結清單中移除目前線程對應的節點(如果隊列裡面有該節點的話),然後抛出 InterruptedException異常;代碼1.2.2如果發現目前任務狀态大于COMPLETING說明任務已經進入了終态(可能是NORMAL、EXCEPTIONAL、CANCELLED、INTERRUPTED中的一種),則把執行任務的線程的引用設定為null,并且傳回結果;
  • 代碼1.2.3如果目前任務狀态為COMPLETING說明任務已經接近完成了,就差設定結果到outCome了,則這時候讓目前線程放棄CPU執行,意在讓任務執行線程擷取到CPU然後讓任務狀态從COMPLETING轉換到終态NORMAL,這樣可以避免目前調用get系列的方法的線程被挂起,然後在被喚醒的開銷;
  • 代碼1.2.4如果目前q為null,則建立一個與目前線程相關的節點,代碼1.2.5如果目前線程對應節點還沒放入到waiters管理的等待清單,則使用CAS操作放入;
  • 代碼1.2.6如果設定了逾時時間則使用LockSupport.parkNanos(this, nanos)讓目前線程挂起deadline時間,否則會調用 LockSupport.park(this);讓線程一直挂起直到其他線程調用了unpark方法,并且以目前線程為參數(比如finishCompletion()方法)。

另外帶逾時參數的V get(long timeout, TimeUnit unit)方法與get()方法類似,隻是添加了逾時時間,這裡不再累述。

3.5 FutureTask的cancel(boolean mayInterruptIfRunning)方法

嘗試取消任務的執行,如果目前任務已經完成或者任務已經被取消了,則嘗試取消任務會失敗;如果任務還沒被執行時候,調用了取消任務,則任務将永遠不會被執行;如果任務已經開始運作了,這時候取消任務,則參數mayInterruptIfRunning将決定是否要将正在執行任務的線程中斷,如果為true則辨別要中斷,否則辨別不中斷;

當調用取消任務後,在調用isDone()方法,後者會傳回true,随後調用isCancelled()方法也會一直傳回true;該方法會傳回false,如果任務不能被取消,比如任務已經完成了,任務已經被取消了。

cancel方法的代碼如下:

public boolean cancel(boolean mayInterruptIfRunning) {
    //1.如果任務狀态為New則使用CAS設定任務狀态為INTERRUPTING或者CANCELLED
    if (!(state == NEW &&
          UNSAFE.compareAndSwapInt(this, stateOffset, NEW,
              mayInterruptIfRunning ? INTERRUPTING : CANCELLED)))
        return false;
    //2.如果設定了中斷正常執行任務線程,則中斷
    try {    
        if (mayInterruptIfRunning) {
            try {
                Thread t = runner;
                if (t != null)
                    t.interrupt();
            } finally { // final state
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
        //3.移除并激活所有因為等待結果而被阻塞的線程
        finishCompletion();
    }
    return true;
}           
  • 如上代碼1,如果任務狀态為New則使用CAS設定任務狀态為INTERRUPTING或者CANCELLED,如果mayInterruptIfRunning設定為了true則說明要中斷正在執行任務的線程,則使用CAS設定任務狀态為INTERRUPTING,否則設定為CANCELLED;如果CAS失敗則直接傳回false。
  • 如果CAS成功,則說明目前任務狀态已經為INTERRUPTING或者CANCELLED,如果mayInterruptIfRunning為true則中斷執行任務的線程,然後設定任務狀态為INTERRUPTED。
  • 最後代碼3移除并激活所有因為等待結果而被阻塞的線程。

另外我們可以使用isCancelled()方法判斷一個任務是否被取消了,使用isDone()方法判斷一個任務是否處于終态了。

總結:當我們建立一個FutureTask時候,其任務狀态初始化為NEW,當我們把任務送出到線程或者線程池後,會有一個線程來執行該FutureTask任務,具體是調用其run方法來執行任務。在任務執行過程中,我們可以在其他線程調用FutureTask的get()方法來等待擷取結果,如果目前任務還在執行,則調用get的線程會被阻塞然後放入FutureTask内的阻塞連結清單隊列;多個線程可以同時調用get方法,這些線程可能都會被阻塞到了阻塞連結清單隊列。當任務執行完畢後會把結果或者異常資訊設定到outcome變量,然後會移除和喚醒FutureTask内的阻塞連結清單隊列裡面的線程節點,然後這些由于調用FutureTask的get方法而被阻塞的線程就會被激活。

3.6 FutureTask的局限性

FutureTask雖然提供了用來檢查任務是否執行完成、等待任務執行結果、擷取任務執行結果的方法,但是這些特色并不足以讓我們寫出簡潔的并發代碼。比如它并不能清楚的表達出多個FutureTask之間的關系,另外為了從Future擷取結果,我們必須調用get()方法,而該方法還是會在任務執行完畢前阻塞調用線程的,這明顯不是我們想要的。

我們真正要想要的是:

  • 可以将兩個或者多個異步計算結合在一起變成一個,這包含兩個或者多個異步計算是互相獨立的時候或者第二個異步計算依賴第一個異步計算結果的時候。
  • 對反應式程式設計的支援,也就是當任務計算完成後能進行通知,并且可以以計算結果作為一個行為動作的參數進行下一步計算,而不是僅僅提供調用線程以阻塞的方式擷取計算結果。
  • 可以通過程式設計的方式手動設定(代碼的方式)Future的結果;FutureTask則不可以讓使用者通過函數來設定其計算結果,而是其任務内部來進行設定。
  • 可以等多個Future對應的計算結果都出來後做一些事情

為了克服FutureTask的局限性,以及滿足我們對異步程式設計的需要,JDK8中提供了CompletableFuture,CompletableFuture是一個可以通過程式設計方式顯式的設定計算結果和狀态以便讓任務結束的Future,本書後面章節我們會具體講解。

四、總結

《Java異步程式設計實戰》一書是國内首本系統講解Java異步程式設計的書籍,本書涵蓋了Java中常見的異步程式設計場景:這包含單JVM内的異步程式設計、以及跨主機通過網絡通訊的遠端過程調用的異步調用與異步處理、Web請求的異步處理、以及常見的異步程式設計架構原了解析和golang語言内置的異步程式設計能力。識别下方二維碼即可購買本書:

繼續閱讀