不會用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 的使用,讓我們編寫異步程序也像寫串行代碼那樣簡單,縱享絲滑

接下來我們就瞭解一下 CompletableFuture 的使用

靈魂追問

  1. 你在日常開發工作中是怎樣將整塊任務做到分工與協作的呢?有什麼基本準則嗎?
  2. 如何批量的執行異步任務呢?

參考

  1. Java 併發編程實戰
  2. Java 併發編程的藝術
  3. Java 併發編程之美

個人博客:https://dayarch.top
加我微信好友, 進羣娛樂學習交流,備註「進羣」

歡迎持續關注公衆號:「日拱一兵」

  • 前沿 Java 技術乾貨分享
  • 高效工具彙總 | 回覆「工具」
  • 面試問題分析與解答
  • 技術資料領取 | 回覆「資料」

以讀偵探小說思維輕鬆趣味學習 Java 技術棧相關知識,本着將複雜問題簡單化,抽象問題具體化和圖形化原則逐步分解技術問題,技術持續更新,請持續關注......


發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章