1. 程式人生 > 實用技巧 >不會用Java Future,我懷疑你泡茶沒我快, 又是超長圖文!!(轉載)

不會用Java Future,我懷疑你泡茶沒我快, 又是超長圖文!!(轉載)

不會用Java Future,我懷疑你泡茶沒我快, 又是超長圖文!!

  • 你有一個思想,我有一個思想,我們交換後,一個人就有兩個思想

  • If you can NOT explain it simply, you do NOT understand it well enough

現陸續將Demo程式碼和技術文章整理在一起Github實踐精選,方便大家閱讀檢視,本文同樣收錄在此,覺得不錯,還請Star

前言

建立執行緒有幾種方式?這個問題的答案應該是可以脫口而出的吧

  • 繼承 Thread 類
  • 實現 Runnable 介面

但這兩種方式建立的執行緒是屬於”三無產品“:

  • 沒有引數
  • 沒有返回值
  • 沒辦法丟擲異常
class MyThread implements Runnable{
   @Override
   public void run() {
      log.info("my thread");
   }
}

Runnable 介面是 JDK1.0 的核心產物

 /**
 * @since   JDK1.0
 */
@FunctionalInterface
public interface Runnable {
    public abstract void run();
}

用著 “三無產品” 總是有一些弊端,其中沒辦法拿到返回值是最讓人不能忍的,於是 Callable 就誕生了

Callable

又是 Doug Lea 大師,又是 Java 1.5 這個神奇的版本

 /**
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method {@code call}
 */
@FunctionalInterface
public interface Callable<V> {
    
    V call() throws Exception;
}

Callable 是一個泛型介面,裡面只有一個call()方法,該方法可以返回泛型值 V

,使用起來就像這樣:

Callable<String> callable = () -> {
    // Perform some computation
    Thread.sleep(2000);
    return "Return some result";
};

二者都是函式式介面,裡面都僅有一個方法,使用上又是如此相似,除了有無返回值,Runnable 與 Callable 就點差別嗎?

Runnable VS Callable

兩個介面都是用於多執行緒執行任務的,但他們還是有很明顯的差別的

執行機制

先從執行機制上來看,Runnable 你太清楚了,它既可以用在 Thread 類中,也可以用在ExecutorService類中配合執行緒池的使用;Bu~~~~t, Callable 只能在ExecutorService中使用,你翻遍 Thread 類,也找不到Callable 的身影

異常處理

Runnable 介面中的 run 方法簽名上沒有throws,自然也就沒辦法向上傳播受檢異常;而 Callable 的 call() 方法簽名卻有throws,所以它可以處理受檢異常;

所以歸納起來看主要有這幾處不同點:

整體差別雖然不大,但是這點差別,卻具有重大意義

返回值和處理異常很好理解,另外,在實際工作中,我們通常要使用執行緒池來管理執行緒(原因已經在為什麼要使用執行緒池?中明確說明),所以我們就來看看 ExecutorService 中是如何使用二者的

ExecutorService

先來看一下 ExecutorService 類圖

我將上圖示記的方法單獨放在此處

void execute(Runnable command);

<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

可以看到,使用ExecutorService 的execute()方法依舊得不到返回值,而submit()方法清一色的返回Future型別的返回值

細心的朋友可能已經發現, submit() 方法已經在CountDownLatch 和 CyclicBarrier 傻傻的分不清楚?文章中多次使用了,只不過我們沒有獲取其返回值罷了,那麼

  • Future 到底是什麼呢?
  • 怎麼通過它獲取返回值呢?

我們帶著這些疑問一點點來看

Future

Future 又是一個介面,裡面只有五個方法:

從方法名稱上相信你已經能看出這些方法的作用

// 取消任務
boolean cancel(boolean mayInterruptIfRunning);

// 獲取任務執行結果
V get() throws InterruptedException, ExecutionException;

// 獲取任務執行結果,帶有超時時間限制
V get(long timeout, TimeUnit unit) throws InterruptedException,                             ExecutionException,  TimeoutException;

// 判斷任務是否已經取消
boolean isCancelled();

// 判斷任務是否已經結束
boolean isDone();

鋪墊了這麼多,看到這你也許有些亂了,咱們趕緊看一個例子,演示一下幾個方法的作用

@Slf4j
public class FutureAndCallableExample {

   public static void main(String[] args) throws InterruptedException, ExecutionException {
      ExecutorService executorService = Executors.newSingleThreadExecutor();

      // 使用 Callable ,可以獲取返回值
      Callable<String> callable = () -> {
         log.info("進入 Callable 的 call 方法");
         // 模擬子執行緒任務,在此睡眠 2s,
         // 小細節:由於 call 方法會丟擲 Exception,這裡不用像使用 Runnable 的run 方法那樣 try/catch 了
         Thread.sleep(5000);
         return "Hello from Callable";
      };

      log.info("提交 Callable 到執行緒池");
      Future<String> future = executorService.submit(callable);

      log.info("主執行緒繼續執行");

      log.info("主執行緒等待獲取 Future 結果");
      // Future.get() blocks until the result is available
      String result = future.get();
      log.info("主執行緒獲取到 Future 結果: {}", result);

      executorService.shutdown();
   }
}

程式執行結果如下:

如果你執行上述示例程式碼,主執行緒呼叫 future.get() 方法會阻塞自己,直到子任務完成。我們也可以使用 Future 方法提供的isDone方法,它可以用來檢查 task 是否已經完成了,我們將上面程式做點小修改:

// 如果子執行緒沒有結束,則睡眠 1s 重新檢查
while(!future.isDone()) {
   System.out.println("Task is still not done...");
   Thread.sleep(1000);
}

來看執行結果:

如果子程式執行時間過長,或者其他原因,我們想 cancel 子程式的執行,則我們可以使用 Future 提供的 cancel 方法,繼續對程式做一些修改

while(!future.isDone()) {
   System.out.println("子執行緒任務還沒有結束...");
   Thread.sleep(1000);

   double elapsedTimeInSec = (System.nanoTime() - startTime)/1000000000.0;

 	 // 如果程式執行時間大於 1s,則取消子執行緒的執行
   if(elapsedTimeInSec > 1) {
      future.cancel(true);
   }
}

來看執行結果:

為什麼呼叫 cancel 方法程式會出現 CancellationException 呢? 是因為呼叫 get() 方法時,明確說明了:

呼叫 get() 方法時,如果計算結果被取消了,則丟擲 CancellationException (具體原因,你會在下面的原始碼分析中看到)

有異常不處理是非常不專業的,所以我們需要進一步修改程式,以更友好的方式處理異常

// 通過 isCancelled 方法判斷程式是否被取消,如果被取消,則列印日誌,如果沒被取消,則正常呼叫 get() 方法
if (!future.isCancelled()){
   log.info("子執行緒任務已完成");
   String result = future.get();
   log.info("主執行緒獲取到 Future 結果: {}", result);
}else {
   log.warn("子執行緒任務被取消");
}

檢視程式執行結果:

相信到這裡你已經對Future的幾個方法有了基本的使用印象,但Future是介面,其實使用ExecutorService.submit()方法返回的一直都是Future的實現類FutureTask

接下來我們就進入這個核心實現類一探究竟

FutureTask

同樣先來看類結構

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

很神奇的一個介面,FutureTask實現了RunnableFuture介面,而RunnableFuture介面又分別實現了RunnableFuture介面,所以可以推斷出FutureTask具有這兩種介面的特性:

  • Runnable特性,所以可以用在ExecutorService中配合執行緒池使用
  • Future特性,所以可以從中獲取到執行結果

FutureTask原始碼分析

如果你完整的看過 AQS 相關分析的文章,你也許會發現,閱讀 Java 併發工具類原始碼,我們無非就是要關注以下這三點:

- 狀態 (程式碼邏輯的主要控制)
- 佇列 (等待排隊佇列)
- CAS (安全的set 值)

腦海中牢記這三點,咱們開始看 FutureTask 原始碼,看一下它是如何圍繞這三點實現相應的邏輯的

文章開頭已經提到,實現 Runnable 介面形式建立的執行緒並不能獲取到返回值,而實現 Callable 的才可以,所以 FutureTask 想要獲取返回值,必定是和 Callable 有聯絡的,這個推斷一點都沒錯,從構造方法中就可以看出來:

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

即便在 FutureTask 構造方法中傳入的是 Runnable 形式的執行緒,該構造方法也會通過Executors.callable工廠方法將其轉換為 Callable 型別:

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

但是 FutureTask 實現的是 Runnable 介面,也就是隻能重寫 run() 方法,run() 方法又沒有返回值,那問題來了:

  • FutureTask 是怎樣在 run() 方法中獲取返回值的?
  • 它將返回值放到哪裡了?
  • get() 方法又是怎樣拿到這個返回值的呢?

我們來看一下 run() 方法(關鍵程式碼都已標記註釋)

public void run() {
  	// 如果狀態不是 NEW,說明任務已經執行過或者已經被取消,直接返回
  	// 如果狀態是 NEW,則嘗試把執行執行緒儲存在 runnerOffset(runner欄位),如果賦值失敗,則直接返回
    if (state != NEW ||
        !UNSAFE.compareAndSwapObject(this, runnerOffset,
                                     null, Thread.currentThread()))
        return;
    try {
      	// 獲取建構函式傳入的 Callable 值
        Callable<V> c = callable;
        if (c != null && state == NEW) {
            V result;
            boolean ran;
            try {
              	// 正常呼叫 Callable 的 call 方法就可以獲取到返回值
                result = c.call();
                ran = true;
            } catch (Throwable ex) {
                result = null;
                ran = false;
              	// 儲存 call 方法丟擲的異常
                setException(ex);
            }
            if (ran)
              	// 儲存 call 方法的執行結果
                set(result);
        }
    } finally {        
        runner = null;       
        int s = state;
      	// 如果任務被中斷,則執行中斷處理
        if (s >= INTERRUPTING)
            handlePossibleCancellationInterrupt(s);
    }
}

run()方法沒有返回值,至於run()方法是如何將call()方法的返回結果和異常都儲存起來的呢?其實非常簡單, 就是通過 set(result) 儲存正常程式執行結果,或通過 setException(ex) 儲存程式異常資訊

/** The result to return or exception to throw from get() */
private Object outcome; // non-volatile, protected by state reads/writes

// 儲存異常結果
protected void setException(Throwable t) {
    if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
        outcome = t;
        UNSAFE.putOrderedInt(this, stateOffset, EXCEPTIONAL); // final state
        finishCompletion();
    }
}

// 儲存正常結果
protected void set(V v) {
  if (UNSAFE.compareAndSwapInt(this, stateOffset, NEW, COMPLETING)) {
    outcome = v;
    UNSAFE.putOrderedInt(this, stateOffset, NORMAL); // final state
    finishCompletion();
  }
}

setExceptionset方法非常相似,都是將異常或者結果儲存在Object型別的outcome變數中,outcome是成員變數,就要考慮執行緒安全,所以他們要通過 CAS方式設定 outcome 變數的值,既然是在 CAS 成功後 更改 outcome 的值,這也就是 outcome 沒有被volatile修飾的原因所在。

儲存正常結果值(set方法)與儲存異常結果值(setException方法)兩個方法程式碼邏輯,唯一的不同就是 CAS 傳入的 state 不同。我們上面提到,state 多數用於控制程式碼邏輯,FutureTask 也是這樣,所以要搞清程式碼邏輯,我們需要先對 state 的狀態變化有所瞭解

 /*
 *
 * 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;

7種狀態,千萬別慌,整個狀態流轉其實只有四種線路

FutureTask 物件被創建出來,state 的狀態就是 NEW 狀態,從上面的建構函式中你應該已經發現了,四個最終狀態 NORMAL ,EXCEPTIONAL , CANCELLED , INTERRUPTED 也都很好理解,兩個中間狀態稍稍有點讓人困惑:

  • COMPLETING: outcome 正在被set 值的時候
  • INTERRUPTING:通過 cancel(true) 方法正在中斷執行緒的時候

總的來說,這兩個中間狀態都表示一種瞬時狀態,我們將幾種狀態圖形化展示一下:

我們知道了 run() 方法是如何儲存結果的,以及知道了將正常結果/異常結果儲存到了 outcome 變數裡,那就需要看一下 FutureTask 是如何通過 get() 方法獲取結果的:

public V get() throws InterruptedException, ExecutionException {
    int s = state;
  	// 如果 state 還沒到 set outcome 結果的時候,則呼叫 awaitDone() 方法阻塞自己
    if (s <= COMPLETING)
        s = awaitDone(false, 0L);
  	// 返回結果
    return report(s);
}

awaitDone 方法是 FutureTask 最核心的一個方法

// get 方法支援超時限制,如果沒有傳入超時時間,則接受的引數是 false 和 0L
// 有等待就會有佇列排隊或者可響應中斷,從方法簽名上看有 InterruptedException,說明該方法這是可以被中斷的
private int awaitDone(boolean timed, long nanos)
    throws InterruptedException {
  	// 計算等待截止時間
    final long deadline = timed ? System.nanoTime() + nanos : 0L;
    WaitNode q = null;
    boolean queued = false;
    for (;;) {
      	// 如果當前執行緒被中斷,如果是,則在等待對立中刪除該節點,並丟擲 InterruptedException
        if (Thread.interrupted()) {
            removeWaiter(q);
            throw new InterruptedException();
        }

        int s = state;
      	// 狀態大於 COMPLETING 說明已經達到某個最終狀態(正常結束/異常結束/取消)
      	// 把 thread 只為空,並返回結果
        if (s > COMPLETING) {
            if (q != null)
                q.thread = null;
            return s;
        }
      	// 如果是COMPLETING 狀態(中間狀態),表示任務已結束,但 outcome 賦值還沒結束,這時主動讓出執行權,讓其他執行緒優先執行(只是發出這個訊號,至於是否別的執行緒執行一定會執行可是不一定的)
        else if (s == COMPLETING) // cannot time out yet
            Thread.yield();
      	// 等待節點為空
        else if (q == null)
          	// 將當前執行緒構造節點
            q = new WaitNode();
      	// 如果還沒有入佇列,則把當前節點加入waiters首節點並替換原來waiters
        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);
    }
}

總的來說,進入這個方法,通常會經歷三輪迴圈

  1. 第一輪for迴圈,執行的邏輯是q == null, 這時候會新建一個節點 q, 第一輪迴圈結束。
  2. 第二輪for迴圈,執行的邏輯是!queue,這個時候會把第一輪迴圈中生成的節點的 next 指標指向waiters,然後CAS的把節點q 替換waiters, 也就是把新生成的節點新增到waiters 中的首節點。如果替換成功,queued=true。第二輪迴圈結束。
  3. 第三輪for迴圈,進行阻塞等待。要麼阻塞特定時間,要麼一直阻塞知道被其他執行緒喚醒。

對於第二輪迴圈,大家可能稍稍有點迷糊,我們前面說過,有阻塞,就會排隊,有排隊自然就有佇列,FutureTask 內部同樣維護了一個佇列

/** Treiber stack of waiting threads */
private volatile WaitNode waiters;

說是等待佇列,其實就是一個 Treiber 型別 stack,既然是 stack, 那就像手槍的彈夾一樣(腦補一下子彈放入彈夾的情形),後進先出,所以剛剛說的第二輪迴圈,會把新生成的節點新增到 waiters stack 的首節點

如果程式執行正常,通常呼叫 get() 方法,會將當前執行緒掛起,那誰來喚醒呢?自然是 run() 方法執行完會喚醒,設定返回結果(set方法)/異常的方法(setException方法) 兩個方法中都會呼叫 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
}

將一個任務的狀態設定成終止態只有三種方法:

  • set
  • setException
  • cancel

前兩種方法已經分析完,接下來我們就看一下cancel方法

檢視 Future cancel(),該方法註釋上明確說明三種 cancel 操作一定失敗的情形

  1. 任務已經執行完成了
  2. 任務已經被取消過了
  3. 任務因為某種原因不能被取消

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

  • 如果發起cancel時任務還沒有開始執行,則隨後任務就不會被執行;
  • 如果發起cancel時任務已經在運行了,則這時就需要看mayInterruptIfRunning引數了:
    • 如果mayInterruptIfRunning 為true, 則當前在執行的任務會被中斷
    • 如果mayInterruptIfRunning 為false, 則可以允許正在執行的任務繼續執行,直到它執行完

有了這些鋪墊,看一下 cancel 程式碼的邏輯就秒懂了

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
              	// 修改為最終狀態 INTERRUPTED
                UNSAFE.putOrderedInt(this, stateOffset, INTERRUPTED);
            }
        }
    } finally {
      	// 喚醒等待中的執行緒
        finishCompletion();
    }
    return true;
}

核心方法終於分析完了,到這咱們喝口茶休息一下吧

我是想說,使用 FutureTask 來演練燒水泡茶經典程式

如上圖:

  • 洗水壺 1 分鐘
  • 燒開水 15 分鐘
  • 洗茶壺 1 分鐘
  • 洗茶杯 1 分鐘
  • 拿茶葉 2 分鐘

最終泡茶

讓我心算一下,如果序列總共需要 20 分鐘,但很顯然在燒開水期間,我們可以洗茶壺/洗茶杯/拿茶葉

這樣總共需要 16 分鐘,節約了 4分鐘時間,燒水泡茶尚且如此,在現在高併發的時代,4分鐘可以做的事太多了,學會使用 Future 優化程式是必然(其實優化程式就是尋找關鍵路徑,關鍵路徑找到了,非關鍵路徑的任務通常就可以和關鍵路徑的內容並行執行了

@Slf4j
public class MakeTeaExample {

   public static void main(String[] args) throws ExecutionException, InterruptedException {
      ExecutorService executorService = Executors.newFixedThreadPool(2);

      // 建立執行緒1的FutureTask
      FutureTask<String> ft1 = new FutureTask<String>(new T1Task());
      // 建立執行緒2的FutureTask
      FutureTask<String> ft2 = new FutureTask<String>(new T2Task());

      executorService.submit(ft1);
      executorService.submit(ft2);

      log.info(ft1.get() + ft2.get());
      log.info("開始泡茶");

      executorService.shutdown();
   }

   static class T1Task implements Callable<String> {

      @Override
      public String call() throws Exception {
         log.info("T1:洗水壺...");
         TimeUnit.SECONDS.sleep(1);

         log.info("T1:燒開水...");
         TimeUnit.SECONDS.sleep(15);

         return "T1:開水已備好";
      }
   }

   static class T2Task implements Callable<String> {
      @Override
      public String call() throws Exception {
         log.info("T2:洗茶壺...");
         TimeUnit.SECONDS.sleep(1);

         log.info("T2:洗茶杯...");
         TimeUnit.SECONDS.sleep(2);

         log.info("T2:拿茶葉...");
         TimeUnit.SECONDS.sleep(1);
         return "T2:福鼎白茶拿到了";
      }
   }
}

上面的程式是主執行緒等待兩個 FutureTask 的執行結果,執行緒1 燒開水時間更長,執行緒1希望在水燒開的那一剎那就可以拿到茶葉直接泡茶,怎麼半呢?

那隻需要線上程 1 的FutureTask 中獲取 執行緒 2 FutureTask 的返回結果就可以了,我們稍稍修改一下程式:

@Slf4j
public class MakeTeaExample1 {

   public static void main(String[] args) throws ExecutionException, InterruptedException {
      ExecutorService executorService = Executors.newFixedThreadPool(2);

      // 建立執行緒2的FutureTask
      FutureTask<String> ft2 = new FutureTask<String>(new T2Task());
      // 建立執行緒1的FutureTask
      FutureTask<String> ft1 = new FutureTask<String>(new T1Task(ft2));
      
      executorService.submit(ft1);
      executorService.submit(ft2);

      executorService.shutdown();
   }

   static class T1Task implements Callable<String> {

      private FutureTask<String> ft2;
      public T1Task(FutureTask<String> ft2) {
         this.ft2 = ft2;
      }

      @Override
      public String call() throws Exception {
         log.info("T1:洗水壺...");
         TimeUnit.SECONDS.sleep(1);

         log.info("T1:燒開水...");
         TimeUnit.SECONDS.sleep(15);

         String t2Result = ft2.get();
         log.info("T1 拿到T2的 {}, 開始泡茶", t2Result);
         return "T1: 上茶!!!";
      }
   }

   static class T2Task implements Callable<String> {
      @Override
      public String call() throws Exception {
         log.info("T2:洗茶壺...");
         TimeUnit.SECONDS.sleep(1);

         log.info("T2:洗茶杯...");
         TimeUnit.SECONDS.sleep(2);

         log.info("T2:拿茶葉...");
         TimeUnit.SECONDS.sleep(1);
         return "福鼎白茶";
      }
   }
}

來看程式執行結果:

知道這個變化後我們再回頭看 ExecutorService 的三個 submit 方法:

<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);
<T> Future<T> submit(Callable<T> task);

第一種方法,逐層程式碼檢視到這裡:

你會發現,和我們改造燒水泡茶的程式思維是相似的,可以傳進去一個 result,result 相當於主執行緒和子執行緒之間的橋樑,通過它主子執行緒可以共享資料

第二個方法引數是 Runnable 型別引數,即便呼叫 get() 方法也是返回 null,所以僅是可以用來斷言任務已經結束了,類似 Thread.join()

第三個方法引數是 Callable 型別引數,通過get() 方法可以明確獲取 call() 方法的返回值

到這裡,關於 Future 的整塊講解就結束了,還是需要簡單消化一下的

總結

如果熟悉 Javascript 的朋友,Future 的特性和 Javascript 的 Promise 是類似的,私下開玩笑通常將其比喻成男朋友的承諾

迴歸到Java,我們從 JDK 的演變歷史,談及 Callable 的誕生,它彌補了 Runnable 沒有返回值的空缺,通過簡單的 demo 瞭解 Callable 與 Future 的使用。 FutureTask 又是 Future介面的核心實現類,通過閱讀原始碼瞭解了整個實現邏輯,最後結合FutureTask 和執行緒池演示燒水泡茶程式,相信到這裡,你已經可以輕鬆獲取執行緒結果了

燒水泡茶是非常簡單的,如果更復雜業務邏輯,以這種方式使用 Future 必定會帶來很大的會亂(程式結束沒辦法主動通知,Future 的連結和整合都需要手動操作)為了解決這個短板,沒錯,又是那個男人 Doug Lea,CompletableFuture工具類在 Java1.8 的版本出現了,搭配 Lambda 的使用,讓我們編寫非同步程式也像寫序列程式碼那樣簡單,縱享絲滑

原文連結https://www.cnblogs.com/FraserYu/p/13277747.html