JAVA多線程(二十四)Java多線程之CompletableFuture類

1.JAVA多線程(二十四)Java多線程之CompletableFuture類

1.1 什麼是Future

  Future是Java 5添加的類,用來描述一個異步計算的結果。你可以使用isDone方法檢查計算是否完成,或者使用get阻塞住調用線程,直到計算完成返回結果,你也可以使用cancel方法停止任務的執行。

package com.yuanxw.chapter24;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;

public class FutureExample {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        // 構造一個線程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        // 提交線程任務
        Future<Integer> future = executorService.submit(() -> {
            try {
                System.out.println(String.format("當前【%s】線程工作-開始...", Thread.currentThread().getName()));
                TimeUnit.SECONDS.sleep(10);
                System.out.println(String.format("當前【%s】線程工作-結束...", Thread.currentThread().getName()));
                return ThreadLocalRandom.current().nextInt(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
                return -1;
            }
        });
        System.out.println(String.format("【%s】在等待線程執行結果...", Thread.currentThread().getName()));
        System.out.println("是否執行結束:"+future.isDone());
        System.out.println(future.get());
        System.out.println("是否執行結束:"+future.isDone());
    }
}

執行結果:

當前【pool-1-thread-1】線程工作-開始...
【main】在等待線程執行結果...
是否執行結束:false
當前【pool-1-thread-1】線程工作-結束...
76
是否執行結束:true

1.2 Future的侷限性

  雖然Future以及相關使用方法提供了異步執行任務的能力,但是對於結果的獲取卻是很不方便,只能通過阻塞或者輪詢的方式得到任務的結果。阻塞的方式顯然和我們的異步編程的初衷相違背,輪詢的方式又會耗費無謂的CPU資源,而且也不能及時地得到計算結果。CompletableFuture的出現解決了Future模式的缺點。

  • 不能手動完成:
    • 當你寫了一個函數,用於通過一個遠程API獲取一個電子商務產品最新價格。因爲這個 API 太耗時,你把它允許在一個獨立的線程中,並且從你的函數中返回一個 Future。現在假設這個API服務宕機了,這時你想通過該產品的最新緩存價格手工完成這個Future 。你會發現無法這樣做。
  • Future的結果在非阻塞的情況下,不能執行更進一步的操作:
    • Future 不會通知你它已經完成了,它提供了一個阻塞的 get() 方法通知你結果。你無法給 Future 植入一個回調函數,當 Future 結果可用的時候,用該回調函數自動的調用 Future 的結果。
  • 多個Future不能串聯在一起組成鏈式調用:
    • 有時候你需要執行一個長時間運行的計算任務,並且當計算任務完成的時候,你需要把它的計算結果發送給另外一個長時間運行的計算任務等等。你會發現你無法使用 Future 創建這樣的一個工作流。
      不能組合多個 Future 的結果
      假設你有10個不同的Future,你想並行的運行,然後在它們運行未完成後運行一些函數。你會發現你也無法使用 Future 這樣做。
  • 沒有異常處理:
    • Future API 沒有任務的異常處理結構居然有如此多的限制,幸好我們有CompletableFuture,你可以使用 CompletableFuture 達到以上所有目的。

1.3 什麼是CompletableFuture

   在Java中CompletableFuture用於異步編程,異步編程是編寫非阻塞的代碼,運行的任務在一個單獨的線程,與主線程隔離,並且會通知主線程它的進度,成功或者失敗。在這種方式中,主線程不會被阻塞,不需要一直等到子線程完成。主線程可以並行的執行其他任務。使用這種並行方式,可以極大的提高程序的性能。
   CompletableFuture 實現了 Future 和 CompletionStage接口,並且提供了許多關於創建,鏈式調用和組合多個 Future 的便利方法集,而且有廣泛的異常處理支持。
   CompletableFuture和Java8的Stream搭配使用,使用對於一些並行訪問的耗時操作有很大的操作。

CompletableFuture繼承結構關係圖

在這裏插入圖片描述
CompletableFuture類實現了CompletionStage和Future接口,所以你還是可以像以前一樣通過阻塞或者輪詢的方式獲得結果,儘管這種方式不推薦使用。

1.4 CompletableFuture方法分類

1.4.1. 創建CompletableFuture對象

  以Async結尾並且沒有指定Executor的方法會使用ForkJoinPool.commonPool()作爲它的線程池執行異步代碼。runAsync方法也好理解,它以Runnable函數式接口類型爲參數,所以CompletableFuture的計算結果爲空。supplyAsync方法以Supplier<U>函數式接口類型爲參數,CompletableFuture的計算結果類型爲U。
CompletableFuture的靜態工廠方法:

方法名 描述
runAsync(Runnable runnable) 使用ForkJoinPool.commonPool()作爲它的線程池執行異步代碼。
runAsync(Runnable runnable, Executor executor) 使用指定的thread pool執行異步代碼。
supplyAsync(Supplier supplier) 使用ForkJoinPool.commonPool()作爲它的線程池執行異步代碼,異步操作有返回值。
supplyAsync(Supplier supplier, Executor executor) 使用指定的thread pool執行異步代碼,異步操作有返回值。
  • CompletableFuture.completedFuture是一個靜態輔助方法,用來返回一個已經計算好的CompletableFuture。
    /**
     * Returns a new CompletableFuture that is already completed with
     * the given value.
     *
     * @param value the value
     * @param <U> the type of the value
     * @return the completed CompletableFuture
     */
    public static <U> CompletableFuture<U> completedFuture(U value) {
        return new CompletableFuture<U>((value == null) ? NIL : value);
    }
  • 而以下四個靜態方法用來爲一段異步執行的代碼創建CompletableFuture對象:
    /**
     * Returns a new CompletableFuture that is asynchronously completed
     * by a task running in the {@link ForkJoinPool#commonPool()} with
     * the value obtained by calling the given Supplier.
     *
     * @param supplier a function returning the value to be used
     * to complete the returned CompletableFuture
     * @param <U> the function's return type
     * @return the new CompletableFuture
     */
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
        return asyncSupplyStage(asyncPool, supplier);
    }

    /**
     * Returns a new CompletableFuture that is asynchronously completed
     * by a task running in the given executor with the value obtained
     * by calling the given Supplier.
     *
     * @param supplier a function returning the value to be used
     * to complete the returned CompletableFuture
     * @param executor the executor to use for asynchronous execution
     * @param <U> the function's return type
     * @return the new CompletableFuture
     */
    public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor) {
        return asyncSupplyStage(screenExecutor(executor), supplier);
    }

    /**
     * Returns a new CompletableFuture that is asynchronously completed
     * by a task running in the {@link ForkJoinPool#commonPool()} after
     * it runs the given action.
     *
     * @param runnable the action to run before completing the
     * returned CompletableFuture
     * @return the new CompletableFuture
     */
    public static CompletableFuture<Void> runAsync(Runnable runnable) {
        return asyncRunStage(asyncPool, runnable);
    }

    /**
     * Returns a new CompletableFuture that is asynchronously completed
     * by a task running in the given executor after it runs the given
     * action.
     *
     * @param runnable the action to run before completing the
     * returned CompletableFuture
     * @param executor the executor to use for asynchronous execution
     * @return the new CompletableFuture
     */
    public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor) {
        return asyncRunStage(screenExecutor(executor), runnable);
    }

1.4.2. 主動完成計算

方法名 描述
get() 方法同步等待結果。
get(timeout,unit) get(timeout,unit)簽名方法,給定時間,然後返回其結果,如果超時,拋出異常
join() 完成後返回結果值,如果完成異常,則返回(未檢查)異常。
getNow() 如果已完成,則返回結果值(或拋出任何遇到的異常),否則返回給定的值IfAbsent。
package com.yuanxw.chapter24;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

public class CompletableFutureExample1 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        runAsync();
        // get() 方法同步等待結果。
        get();

        // get(timeout,unit)簽名方法,給定時間,然後返回其結果,如果超時,拋出異常
        get(3,TimeUnit.SECONDS);

        // join() 完成後返回結果值,如果完成異常,則返回(未檢查)異常。
        join();

        // getNow() 如果已完成,則返回結果值(或拋出任何遇到的異常),否則返回給定的值IfAbsent。
        getNow();
    }

    /**
     * runAsync()異步執行runAsync返回的CompletableFuture是沒有返回值的。
     * supplyAsync()異步執行runAsync返回的CompletableFuture是有返回值的。
     執行結果:
     =====【main】執行runAsync()簽名方法-開始=====
     Hello world!!!
     =====【main】執行runAsync()簽名方法-結束=====
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private static void runAsync() throws ExecutionException, InterruptedException {
        System.out.println(String.format("=====【%s】執行runAsync()簽名方法-開始=====", Thread.currentThread().getName()));
        CompletableFuture.runAsync(() -> System.out.println("Hello world!!!")).get();
        System.out.println(String.format("=====【%s】執行runAsync()簽名方法-結束=====", Thread.currentThread().getName()));
    }

    /**
     get() 方法同步等待結果。
     執行結果:
          =====【main】執行get()簽名方法-開始=====
          get()簽名方法,執行結果:4
          =====【main】執行get()簽名方法-結束=====
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private static void get() throws ExecutionException, InterruptedException {
        System.out.println(String.format("=====【%s】執行get()簽名方法-開始=====", Thread.currentThread().getName()));
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            // 睡眠5秒
            processSleep(5);
            return ThreadLocalRandom.current().nextInt(10) * 2;
        });
        /** get() 方法同步等待結果。 **/
        System.out.println("get()簽名方法,執行結果:"+completableFuture.get());
        System.out.println(String.format("=====【%s】執行get()簽名方法-結束=====", Thread.currentThread().getName()));
    }

    /**
     get(timeout,unit)簽名方法,給定時間,然後返回其結果,如果超時,拋出異常
     執行結果:
     =====【main】執行get(timeout,unit)簽名方法-開始=====
     java.util.concurrent.TimeoutException
     	at java.util.concurrent.CompletableFuture.timedGet(CompletableFuture.java:1771)
     	at java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1915)
     	at com.yuanxw.chapter24.CompletableFutureExample.get(CompletableFutureExample.java:53)
     	at com.yuanxw.chapter24.CompletableFutureExample.main(CompletableFutureExample.java:11)
     =====【main】執行get(timeout,unit)簽名方法-結束=====
     *
     * @param timeout
     * @param unit
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private static void get(long timeout, TimeUnit unit) throws ExecutionException, InterruptedException {
        System.out.println(String.format("=====【%s】執行get(timeout,unit)簽名方法-開始=====", Thread.currentThread().getName()));
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            // 睡眠5秒
            processSleep(5);
            return ThreadLocalRandom.current().nextInt(10) * 2;
        });
        try {
            System.out.println("get(timeout,unit)簽名方法,執行結果:"+completableFuture.get(timeout,unit));
        } catch (TimeoutException e) {
            e.printStackTrace();
        }
        System.out.println(String.format("=====【%s】執行get(timeout,unit)簽名方法-結束=====", Thread.currentThread().getName()));
    }

    /**
      join() 完成後返回結果值,如果完成異常,則返回(未檢查)異常。
     執行結果:
         =====【main】執行join()簽名方法-開始=====
         join()簽名方法,執行結果:40
         =====【main】執行join()簽名方法-結束=====
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private static void join() throws ExecutionException, InterruptedException {
        System.out.println(String.format("=====【%s】執行join()簽名方法-開始=====", Thread.currentThread().getName()));
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            // 睡眠5秒
            processSleep(5);
            return ThreadLocalRandom.current().nextInt(10) * 5;
        });
        /** join() 完成後返回結果值,如果完成異常,則返回(未檢查)異常。 **/
        System.out.println("join()簽名方法,執行結果:"+ completableFuture.join());
        System.out.println(String.format("=====【%s】執行join()簽名方法-結束=====", Thread.currentThread().getName()));
    }

    /**
     getNow() 如果已完成,則返回結果值(或拋出任何遇到的異常),否則返回給定的值IfAbsent。
     執行結果:
      =====【main】執行getNow()簽名方法-開始=====
      getNow()簽名方法,執行結果:===unknown===
      getNow()簽名方法,執行結果:N
      =====【main】getNow()簽名方法-結束=====
     */
    private static void getNow() {
        System.out.println(String.format("=====【%s】執行getNow()簽名方法-開始=====", Thread.currentThread().getName()));
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            // 睡眠5秒
            processSleep(5);
            // 生成隨機字符
            char c = (char) (ThreadLocalRandom.current().nextInt(25 )+ 65);
            return String.valueOf(c);
        });
        /** getNow() 如果已完成,則返回結果值(或拋出任何遇到的異常),否則返回給定的值IfAbsent。 **/
        System.out.println("getNow()簽名方法,執行結果:"+ completableFuture.getNow("===unknown==="));
        processSleep(6);
        System.out.println("getNow()簽名方法,執行結果:"+ completableFuture.getNow("===unknown==="));
        System.out.println(String.format("=====【%s】getNow()簽名方法-結束=====", Thread.currentThread().getName()));
    }

    /**
     * 線程休眠的方法
     * @param seconds
     */
    private static void processSleep(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

1.4.3. 計算結果完成時的處理

   當CompletableFuture計算結果完成時,我們需要對結果進行處理,或者當CompletableFuture產生異常的時候需要對異常進行處理。有如下幾種方法:

方法名 描述
whenComplete(BiConsumer<? super T, ? super Throwable> action) 當CompletableFuture完成計算結果時對結果進行處理,或者當CompletableFuture產生異常的時候對異常進行處理。
whenCompleteAsync(BiConsumer<? super T, ? super Throwable> action) 當CompletableFuture完成計算結果時對結果進行處理,或者當CompletableFuture產生異常的時候對異常進行處理。使用ForkJoinPool。
BiConsumer<? super T, ? super Throwable> action, Executor executor) 當CompletableFuture完成計算結果時對結果進行處理,或者當CompletableFuture產生異常的時候對異常進行處理。使用指定的線程池。
package com.yuanxw.chapter24;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class CompletableFutureExample2 {
    public static void main(String[] args) throws InterruptedException {
        whenComplete();
        Thread.currentThread().join();
    }

    /**
     * runAsync()異步執行runAsync返回的CompletableFuture是沒有返回值的。
     * supplyAsync()異步執行runAsync返回的CompletableFuture是有返回值的。
     執行結果:
     =====【main】執行runAsync()簽名方法-開始=====
     Hello world!!!
     =====【main】執行runAsync()簽名方法-結束=====
     */

    /**
     * whenComplete():當CompletableFuture完成計算結果時對結果進行處理,或者當CompletableFuture產生異常的時候對異常進行處理。
     執行結果:
         =====執行whenComplete()簽名方法-開始=====
         執行supplyAsync()簽名方法-結束>>>>>
         統計字符串長度:14
         =====執行whenComplete()簽名方法-結束=====
     */
    private static void whenComplete()  {
        System.out.println("=====執行whenComplete()簽名方法-開始=====");
        CompletableFuture.supplyAsync(() ->{
            // 睡眠5秒
            processSleep(5);
            System.out.println("執行supplyAsync()簽名方法-結束>>>>>");
            return "Hello world!!!";
        }).whenComplete((s, throwable) -> {
            System.out.println("統計字符串長度:"+s.length());
            System.out.println("=====執行whenComplete()簽名方法-結束=====");
        });
    }

    /**
     * 線程休眠的方法
     * @param seconds
     */
    private static void processSleep(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

1.4.4. 計算結果完成時的轉換

   這裏同樣也是返回CompletableFuture,但是這個結果會由我們自定義返回去轉換他,同樣的不以Async結尾的方法由原來的線程計算,以Async結尾的方法由默認的線程池ForkJoinPool.commonPool()或者指定的線程池executor運行。

方法名 描述
public <U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn) 接受一個Function<? super T,? extends U>參數用來轉換CompletableFuture。
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn) 接受一個Function<? super T,? extends U>參數用來轉換CompletableFuture,使用ForkJoinPool。
public <U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor) 接受一個Function<? super T,? extends U>參數用來轉換CompletableFuture,使用指定的線程池。
package com.yuanxw.chapter24;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class CompletableFutureExample3 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        thenApply();
    }

    /**
     thenApply() 當原來的CompletableFuture計算完後,將結果傳遞給函數fn,將fn的結果作爲新的CompletableFuture計算結果。
     因此它的功能相當於將CompletableFuture<T>轉換成CompletableFuture<U>。。
     執行結果:
         =====執行thenApply()簽名方法-開始=====
         thenApply()簽名方法,在supplyAsync階段,執行結果:DFRHHQ
         thenApply()簽名方法,執行結果:###【dfrhhq】###
         =====thenApply()簽名方法-結束=====
     */
    private static void thenApply() throws ExecutionException, InterruptedException {
        System.out.println("=====執行thenApply()簽名方法-開始=====");
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            // 睡眠5秒
            processSleep(5);
            StringBuffer buffer = new StringBuffer();
            IntStream.range(0,6).boxed().forEach(integer -> {
                // 生成隨機字符
                char c = (char) (ThreadLocalRandom.current().nextInt(25 )+ 65);
                buffer.append(c);
            });
            System.out.println("thenApply()簽名方法,在supplyAsync階段,執行結果:" + buffer.toString());
            return buffer.toString();
        }).thenApply(str -> String.format("###【%s】###", str.toLowerCase()));
        System.out.println("thenApply()簽名方法,執行結果:"+ completableFuture.get());
        System.out.println("=====thenApply()簽名方法-結束=====");
    }

    /**
     * 線程休眠的方法
     * @param seconds
     */
    private static void processSleep(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

1.4.5. 計算結果完成時的消費

   這裏還會有一個只會對計算結果消費不會返回任何結果的方法。同樣的不以Async結尾的方法由原來的線程計算,以Async結尾的方法由默認的線程池ForkJoinPool.commonPool()或者指定的線程池executor運行。

方法名 描述
public CompletableFuture thenAccept(Consumer<? super T> action) 當CompletableFuture完成計算結果,只對結果執行Action,而不返回新的計算值。
public CompletableFuture thenAcceptAsync(Consumer<? super T> action) 當CompletableFuture完成計算結果,只對結果執行Action,而不返回新的計算值,使用ForkJoinPool。
public CompletableFuture thenAcceptAsync(Consumer<? super T> action,Executor executor) 當CompletableFuture完成計算結果,只對結果執行Action,而不返回新的計算值。
package com.yuanxw.chapter24;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.TimeUnit;
import java.util.stream.IntStream;

public class CompletableFutureExample4 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        thenAccept();
    }

    /**
     thenAccept() CompletableFuture完成計算結果,只對結果執行Action,而不返回新的計算值。
     執行結果:
     =====執行thenAccept()簽名方法-開始=====
     系統自動生成的6位驗證碼:EHSRHT,該驗證碼5分鐘有效,請勿泄露給他人。
     =====thenAccept()簽名方法-結束=====
     */
    private static void thenAccept() throws ExecutionException, InterruptedException {
        System.out.println("=====執行thenAccept()簽名方法-開始=====");
        CompletableFuture
                .supplyAsync(() -> "系統自動生成的6位驗證碼:")
                .thenApply((str) -> str + verifiCode())
                .thenApply((str) -> str + ",該驗證碼5分鐘有效,請勿泄露給他人。")
                .thenAccept(System.out::println);
        System.out.println("=====thenAccept()簽名方法-結束=====");
    }

    /**
     * 生成6位數驗證碼
     * @return
     */
    private static String verifiCode(){
        StringBuffer buffer = new StringBuffer();
        IntStream.range(0,6).boxed().forEach(integer -> {
            // 生成隨機字符
            char c = (char) (ThreadLocalRandom.current().nextInt(25 )+ 65);
            buffer.append(c);
        });
        return buffer.toString();
    }
}

1.4.6. 計算結果完成時的消費合併結果

   thenAcceptBoth以及相關方法提供了類似的功能,當兩個CompletionStage都正常完成計算的時候,就會執行提供的action,它用來組合另外一個異步的結果。

   runAfterBoth是當兩個CompletionStage都正常完成計算的時候,執行一個Runnable,這個Runnable並不使用計算的結果。

方法名 描述
public <U> CompletableFuture<Void> thenAcceptBoth(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action) 當兩個CompletableFuture都正常完成後,執行提供的action,用它來組合另外一個CompletableFuture的結果。
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action) 當兩個CompletableFuture都正常完成後,執行提供的action,用它來組合另外一個CompletableFuture的結果。使用ForkJoinPool。
public <U> CompletableFuture<Void> thenAcceptBothAsync(CompletionStage<? extends U> other,BiConsumer<? super T, ? super U> action, Executor executor) 當兩個CompletableFuture都正常完成後,執行提供的action,用它來組合另外一個CompletableFuture的結果。使用指定的線程池。
public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,Runnable action) 當兩個CompletionStage都正常完成計算的時候,執行一個Runnable,這個Runnable並不使用計算的結果。
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action) 當兩個CompletionStage都正常完成計算的時候,執行一個Runnable,這個Runnable並不使用計算的結果。使用ForkJoinPool。
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,Runnable action,Executor executor) 當兩個CompletionStage都正常完成計算的時候,執行一個Runnable,這個Runnable並不使用計算的結果。使用指定的線程池。
package com.yuanxw.chapter24;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.TimeUnit;

public class CompletableFutureExample5 {
    public static void main(String[] args) throws InterruptedException {
        thenAcceptBoth();
        runAfterBoth();
        Thread.currentThread().join();
    }
    /**
     thenAcceptBoth() 當你想要使用兩個Future結果時,但不需要將任何結果值進行返回時,
     可以用 thenAcceptBoth ,它表示後續的處理不需要返回值。
     執行結果:
         =====執行thenAcceptBoth()簽名方法-開始=====
         Hello World
         =====thenAcceptBoth()簽名方法-結束=====
     */
    private static void thenAcceptBoth() {
        System.out.println("=====執行thenAcceptBoth()簽名方法-開始=====");
        CompletableFuture.supplyAsync(() -> "Hello")
                .thenAcceptBoth(CompletableFuture.supplyAsync(() -> " World"),
                        (s1, s2) -> System.out.println(s1 + s2));
        System.out.println("=====thenAcceptBoth()簽名方法-結束=====");
    }

    /**
     * runAfterBoth是當兩個CompletionStage都正常完成計算的時候,
     執行一個Runnable,這個Runnable並不使用計算的結果。
     執行結果:
          =====執行runAfterBoth()簽名方法-開始=====
          ForkJoinPool.commonPool-worker-1線程,工作-開始>>
          ForkJoinPool.commonPool-worker-2線程,工作-開始>>
          =====runAfterBoth()簽名方法-結束=====
          ForkJoinPool.commonPool-worker-2線程,工作-結束<<
          ForkJoinPool.commonPool-worker-1線程,工作-結束<<
          ===執行任務結束===
     */
    private static void runAfterBoth() {
        System.out.println("=====執行runAfterBoth()簽名方法-開始=====");
        CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "線程,工作-開始>>");
            // 睡眠5秒
            processSleep(5);
            System.out.println(Thread.currentThread().getName() + "線程,工作-結束<<");
            return 1;
        }).runAfterBoth(CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "線程,工作-開始>>");
            // 睡眠1秒
            processSleep(1);
            System.out.println(Thread.currentThread().getName() + "線程,工作-結束<<");
            return 2;
        }), () -> System.out.println("===執行任務結束==="));

        System.out.println("=====runAfterBoth()簽名方法-結束=====");
    }

    /**
     * 線程休眠的方法
     * @param seconds
     */
    private static void processSleep(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

1.4.7. 對計算結果的組合

   對於Compose可以連接兩個CompletableFuture,其內部處理邏輯是當第一個CompletableFuture處理沒有完成時會合併成一個CompletableFuture,如果處理完成,第二個future會緊接上一個CompletableFuture進行處理。

方法名 描述
public <U,V> CompletableFuture<V> thenCombine(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn) 當兩個CompletableFuture都正常完成後,執行提供的fn,用它來組合另外一個CompletableFuture的結果。
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn) 當兩個CompletableFuture都正常完成後,執行提供的fn,用它來組合另外一個CompletableFuture的結果。使用ForkJoinPool。
public <U,V> CompletableFuture<V> thenCombineAsync(CompletionStage<? extends U> other,BiFunction<? super T,? super U,? extends V> fn, Executor executor) 當兩個CompletableFuture都正常完成後,執行提供的fn,用它來組合另外一個CompletableFuture的結果。使用指定的線程池。
package com.yuanxw.chapter24;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureExample6 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        thenCombine();
    }

    /**
     * 執行thenCombine()對於Compose可以連接兩個CompletableFuture,
     * 其內部處理邏輯是當第一個CompletableFuture處理沒有完成時會合併成一個CompletableFuture,
     * 如果處理完成,第二個future會緊接上一個CompletableFuture進行處理。
      執行結果:
         =====執行thenCombine()簽名方法-開始=====
         thenCombine()簽名方法,執行結果:計算結果:30
         =====thenCombine()簽名方法-結束=====
     */
    private static void thenCombine() throws ExecutionException, InterruptedException {
        System.out.println("=====執行thenCombine()簽名方法-開始=====");
        CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 10);
        CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 20);
        CompletableFuture<String> completableFuture = future1.thenCombine(future2, (x, y) -> "計算結果:" + (x + y));
        System.out.println("thenCombine()簽名方法,執行結果:"+ completableFuture.get());
        System.out.println("=====thenCombine()簽名方法-結束=====");
    }
}

1.4.8. 對計算結果的異常處理

   當CompletableFuture的計算結果完成,或者拋出異常的時候,可以通過handle方法對結果進行處理。
   exceptionally() 方法將導致CompletableFuture 內發生問題的異常拋出。

方法名 描述
public CompletableFuture handle(BiFunction<? super T, Throwable, ? extends U> fn) 當CompletableFuture的計算結果完成,或者拋出異常的時候,可以通過handle方法對結果進行處理。
public CompletableFuture handleAsync(BiFunction<? super T, Throwable, ? extends U> fn) 當CompletableFuture的計算結果完成,或者拋出異常的時候,可以通過handle方法對結果進行處理。使用ForkJoinPool。
public CompletableFuture handleAsync(BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) 當CompletableFuture的計算結果完成,或者拋出異常的時候,可以通過handle方法對結果進行處理。使用指定的線程池。
public CompletableFuture exceptionally(Function<Throwable, ? extends T> fn) exceptionally() 方法將導致CompletableFuture 內發生問題的異常拋出。
package com.yuanxw.chapter24;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;

public class CompletableFutureExample7 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        handle();
        exceptionally();
    }

    /**
     handle() 當CompletableFuture的計算結果完成,或者拋出異常的時候,可以通過handle方法對結果進行處理。
     執行結果:
         =====handle()簽名方法-開始=====
         handle()簽名方法,在supplyAsync階段,執行1/0結果。
         handle()簽名方法,執行結果:-1
         =====handle()簽名方法-結束=====
         java.util.concurrent.CompletionException: java.lang.ArithmeticException: / by zero
         at java.util.concurrent.CompletableFuture.encodeThrowable(CompletableFuture.java:273)
         at java.util.concurrent.CompletableFuture.completeThrowable(CompletableFuture.java:280)
         at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1592)
         at java.util.concurrent.CompletableFuture$AsyncSupply.exec(CompletableFuture.java:1582)
         at java.util.concurrent.ForkJoinTask.doExec(ForkJoinTask.java:289)
         at java.util.concurrent.ForkJoinPool$WorkQueue.runTask(ForkJoinPool.java:1056)
         at java.util.concurrent.ForkJoinPool.runWorker(ForkJoinPool.java:1692)
         at java.util.concurrent.ForkJoinWorkerThread.run(ForkJoinWorkerThread.java:157)
         Caused by: java.lang.ArithmeticException: / by zero
         at com.yuanxw.chapter24.CompletableFutureExample7.lambda$handle$0(CompletableFutureExample3.java:26)
         at java.util.concurrent.CompletableFuture$AsyncSupply.run(CompletableFuture.java:1590)
         ... 5 more
     */
    private static void handle() throws ExecutionException, InterruptedException {
        System.out.println("=====handle()簽名方法-開始=====");
        CompletableFuture<Integer> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("handle()簽名方法,在supplyAsync階段,執行1/0結果。");
            return 1/0;
        }).handle((integer, throwable) -> {
            if(throwable != null){
                throwable.printStackTrace();
                return -1;
            }
           return integer;
        });

        System.out.println("handle()簽名方法,執行結果:"+ completableFuture.get());
        System.out.println("=====handle()簽名方法-結束=====");
    }

    /**
     * exceptionally() 方法將導致CompletableFuture 內發生問題的異常拋出。
     * 這樣,當執行任務發生異常時,調用get()方法的線程將會收到一個 ExecutionException 異常,
     * 該異常接收了一個包含失敗原因的Exception 參數。
     * 執行結果:
         =====exceptionally()簽名方法-開始=====
         exceptionally()簽名方法,在supplyAsync階段,執行1/0結果。
         exceptionally()簽名方法,執行結果:java.lang.ArrayIndexOutOfBoundsException: 1
         =====exceptionally()簽名方法-結束=====
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private static void exceptionally() throws ExecutionException, InterruptedException {
        System.out.println("=====exceptionally()簽名方法-開始=====");
        CompletableFuture<String> completableFuture = CompletableFuture.supplyAsync(() -> {
            System.out.println("exceptionally()簽名方法,在supplyAsync階段,執行1/0結果。");
            String [] array = new String[]{};
            // 此處異常:java.lang.ArrayIndexOutOfBoundsException: 1
            return array[1];
        }).exceptionally(ex -> ex.getMessage());

        System.out.println("exceptionally()簽名方法,執行結果:"+ completableFuture.get());
        System.out.println("=====exceptionally()簽名方法-結束=====");
    }
}

1.4.9. 其它方法

  allOf方法是當所有的CompletableFuture都執行完後執行計算。anyOf方法是當任意一個CompletableFuture執行完後就會執行計算,計算的結果相同。

方法名 描述
public static CompletableFuture allOf(CompletableFuture<?>… cfs) allOf方法是當所有的CompletableFuture都執行完後執行計算。
public static CompletableFuture anyOf(CompletableFuture<?>… cfs) anyOf方法是當任意一個CompletableFuture執行完後就會執行計算,計算的結果相同。
package com.yuanxw.chapter24;

import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;

public class CompletableFutureExample8 {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        anyAndAllOfCompletableFuture();
    }
    /**
     allOf方法是當所有的CompletableFuture都執行完後執行計算。
     anyOf方法是當任意一個CompletableFuture執行完後就會執行計算,計算的結果相同。
     執行結果:
         =====anyAndAllOfCompletableFuture()簽名方法-開始=====
         ForkJoinPool.commonPool-worker-1線程,工作-開始>>
         ForkJoinPool.commonPool-worker-2線程,工作-開始>>
         ForkJoinPool.commonPool-worker-2線程,工作-結束<<
         anyOf()簽名方法,執行結果:result-2
         ForkJoinPool.commonPool-worker-1線程,工作-結束<<
         =====anyAndAllOfCompletableFuture()簽名方法-結束=====
     */
    private static void anyAndAllOfCompletableFuture() throws ExecutionException, InterruptedException {
        System.out.println("=====anyAndAllOfCompletableFuture()簽名方法-開始=====");

        CompletableFuture<String> completableFuture1=CompletableFuture.supplyAsync(()->{
            //模擬執行耗時任務
            System.out.println(Thread.currentThread().getName() + "線程,工作-開始>>");
            // 睡眠5秒
            processSleep(5);
            System.out.println(Thread.currentThread().getName() + "線程,工作-結束<<");
            //返回結果
            return "result-1";
        });

        CompletableFuture<String> completableFuture2=CompletableFuture.supplyAsync(()->{
            //模擬執行耗時任務
            System.out.println(Thread.currentThread().getName() + "線程,工作-開始>>");
            // 睡眠5秒
            processSleep(1);
            System.out.println(Thread.currentThread().getName() + "線程,工作-結束<<");
            //返回結果
            return "result-2";
        });

        CompletableFuture<Object> anyOfCompletableFuture = CompletableFuture.anyOf(completableFuture1,completableFuture2);
        System.out.println("anyOf()簽名方法,執行結果:"+ anyOfCompletableFuture.get());

        CompletableFuture<Void> allOfCompletableFuture = CompletableFuture.allOf(completableFuture1,completableFuture2);
        // 阻塞等待所有任務執行完成
        allOfCompletableFuture.join();
        System.out.println("=====anyAndAllOfCompletableFuture()簽名方法-結束=====");
    }

    /**
     * 線程休眠的方法
     * @param seconds
     */
    private static void processSleep(long seconds) {
        try {
            TimeUnit.SECONDS.sleep(seconds);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

    – 以上爲《JAVA多線程(二十四)Java多線程之CompletableFuture類》,如有不當之處請指出,我後續逐步完善更正,大家共同提高。謝謝大家對我的關注。

——厚積薄發(yuanxw)

發佈了125 篇原創文章 · 獲贊 166 · 訪問量 47萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章