CompletableFuture使用

152 阅读4分钟

CompletableFuture和Future的区别

想异步执行任务并获取任务的返回值,可以使用Future+线程池组合完成:首先任务实现Callable接口,然后调用Executor.submit()执行任务,最后使用Future.get()获取结果。

public static void main(String[] args) throws Exception {
    Clock clock = Clock.systemDefaultZone();
    ExecutorService executorService = Executors.newFixedThreadPool(1);

    Future<String> task = executorService.submit(() -> {
        System.out.println(Thread.currentThread().getName() + ": " + clock.millis());
        Thread.sleep(3000);
        return "task";
    });
    
    System.out.println("result = " + task.get());
    System.out.println(Thread.currentThread().getName() + ": " + clock.millis());
    executorService.shutdown();
}

Future对于异步线程执行结果的获取,有同步阻塞和轮询查询的方式:

  • 通过future.get()同步阻塞获取结果
  • 通过future.isDone()不断查询任务是否完成,然后再获取结果

而CompletableFuture实现了Future接口,它提供了一系列回调方法,可以在异步任务完成之后通知回调方法执行。

创建任务

CompletableFuture可以使用runAsync和supplyAsync方法创建异步任务:

  • runAsync没有返回值
public static void main(String[] args) {
    ExecutorService executorService = Executors.newFixedThreadPool(1);

    // 默认线程池为ForkJoinPool.commonPool
    CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> {
        System.out.println(Thread.currentThread().getName());
    });

    // 自定义线程池
    CompletableFuture<Void> future2 = CompletableFuture.runAsync(() -> {
        System.out.println(Thread.currentThread().getName());
    }, executorService);

    executorService.shutdown();
}

// 输出
ForkJoinPool.commonPool-worker-1
pool-1-thread-1
  • supplyAsync()方法支持返回值
public static void main(String[] args)throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(1);

    // 默认线程池为ForkJoinPool.commonPool
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> Thread.currentThread().getName());
    System.out.println(future1.get());

    // 自定义线程池
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> Thread.currentThread().getName(), executorService);
    System.out.println(future2.get());

    executorService.shutdown();
}

// 输出
ForkJoinPool.commonPool-worker-1
pool-1-thread-1

异步回调

CompletableFuture支持异步回调,可以在完成任务之后,再继续执行回调任务,这个过程是通过通知回调机制自动完成的,不需要等待第一个任务执行完后再手动执行第二个任务。

CompletableFuture有以下回调函数:

  • thenRun和thenRunAsync:该方法没有参数值,也没有返回值
  • thenAccept和thenAcceptAsync:该方法有参数值,但没有返回值
  • thenApply和thenApplyAsync:该方法有参数值,也有返回值
  • exceptionally:任务执行异常时执行,异常为参数值
  • whenComplete:任务执行完后执行,参数值有两个,第一个是任务的返回值,第二个是异常值
  • handle:和whenComplete一样,但有返回值

thenRun和thenRunAsync

thenRun和thenRunAsync回调方法没有参数值,也没有返回值,例如:

public static void main(String[] args) throws Exception {
    ExecutorService executorService = Executors.newFixedThreadPool(1);

    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
        String threadName = Thread.currentThread().getName();
        System.out.println("1: " + threadName);
        return threadName;
    });
    future1.thenRun(() -> {
        System.out.println("2: " + Thread.currentThread().getName());
    });

    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
        String threadName = Thread.currentThread().getName();
        System.out.println("3: " + threadName);
        return threadName;
    }, executorService);
    future1.thenRunAsync(() -> System.out.println("4: " + Thread.currentThread().getName()));

    executorService.shutdown();
}

// 输出
1: ForkJoinPool.commonPool-worker-1
2: main
3: pool-1-thread-1
4: ForkJoinPool.commonPool-worker-1

后续分析源码时再讨论执行回调方法的线程是哪一个,建议均使用自定义线程池,可按需控制线程池的大小、队列大小,线程池生命周期等。

thenAccept和thenAcceptAsync

thenAccept和thenAcceptAsync回调方法有参数值,但没有返回值,其参数值是第一个任务执行的结果,例如:

public static void main(String[] args) throws Exception {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        String threadName = Thread.currentThread().getName();
        System.out.println("1: " + threadName);
        return threadName;
    });
    future.thenAccept((lastThreadName) -> {
        System.out.println("2: " + Thread.currentThread().getName());
    });
}

// 输出
1: ForkJoinPool.commonPool-worker-1
2: main

thenApply和thenApplyAsync

thenApply和thenApplyAsync回调方法有参数值,也有返回值,其参数值是第一个任务执行的结果, 例如:

public static void main(String[] args) throws Exception {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        String threadName = Thread.currentThread().getName();
        System.out.println("1: " + threadName);
        return threadName;
    });
    future.thenApply((lastThreadName) -> {
        String threadName = Thread.currentThread().getName();
        System.out.println("2: " + threadName);
        return threadName;
    });
}

// 输出
1: ForkJoinPool.commonPool-worker-1
2: main

exceptionally

exceptionally当任务执行异常时,会执行该回调方法,并且会将异常作为回调方法入参,例如:

public static void main(String[] args) throws Exception {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        String threadName = Thread.currentThread().getName();
        System.out.println("1: " + threadName);
        throw new RuntimeException();
    });
    future.exceptionally((exception) -> {
        System.out.println(exception);
        return "ERROR";
    });
}

// 输出
1: ForkJoinPool.commonPool-worker-1
java.util.concurrent.CompletionException: java.lang.RuntimeException

whenComplete

whenComplete当任务执行完后执行,参数值有两个,第一个是任务的返回值,第二个是异常值,无返回值,但是whenComplete方法返回的CompletableFuture是有返回值的,即上个任务的结果。例如:

public static void main(String[] args) throws Exception {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        String threadName = Thread.currentThread().getName();
        System.out.println("1: " + threadName);
        return "I am supplyAsync";
    });
    CompletableFuture<String> newFuture = future.whenComplete((result, throwable) -> {
        String threadName = Thread.currentThread().getName();
        System.out.println("2: " + threadName);
    });
    System.out.println("3: " + newFuture.get());
}

// 输出
1: ForkJoinPool.commonPool-worker-1
2: main
3: I am supplyAsync

handle

handle当任务执行完后执行,参数值有两个,第一个是任务的返回值,第二个是异常值,有返回值,例如:

public static void main(String[] args) throws Exception {
    CompletableFuture<String> future = CompletableFuture.supplyAsync(() -> {
        String threadName = Thread.currentThread().getName();
        System.out.println("1: " + threadName);
        return "I am supplyAsync";
    });
    future.handle((lastThreadName, throwable) -> {
        String threadName = Thread.currentThread().getName();
        System.out.println("2: " + threadName);
        return "I am handle";
    });
    System.out.println("3: "+future.get());
}


// 输出
1: ForkJoinPool.commonPool-worker-1
2: main

任务组合

CompletableFuture可以组合多个异步任务,组合的方法有如下几个:

And关系

将两个或多个CompletableFuture组合起来,只有都正常执行完了,才会执行某个任务:

  • runAfterBoth:组合两个任务,无入参,无返回值
  • thenAcceptBoth:组合两个任务,两个任务的执行结果作为方法入参,无返回值
  • thenCombine:组合两个任务,两个任务的执行结果作为方法入参,有返回值
public static void main(String[] args) throws Exception {
    CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> "1");
    CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> "2");
    CompletableFuture<String> future3 = future1.thenCombine(future2, (t1, t2) -> t1 + "_" + t2);

    System.out.println(future3.get());
}

// 输出
1_2
  • allOf:组合多个任务,没有返回值
public static void main(String[] args) throws Exception {
    List<CompletableFuture<Integer>> futureList = new ArrayList<>();
    for (int i = 0; i < 4; i++) {
        int result = i + 1;
        futureList.add(CompletableFuture.supplyAsync(() -> result));
    }
    // 组合任务
    CompletableFuture<Integer> future = CompletableFuture.allOf(futureList.toArray(new CompletableFuture[0]))
            .handle((unused, throwable) -> {
                Integer result = 0;
                for (CompletableFuture<Integer> sonFuture : futureList) {
                    try {
                        result += sonFuture.get();
                    } catch (InterruptedException | ExecutionException e) {
                        throw new RuntimeException(e);
                    } finally {
                        sonFuture.cancel(true);
                    }
                }
                return result;
            });

    System.out.println(future.get());
}

// 输出
10

Or关系

将两个CompletableFuture组合起来,只要其中一个执行完了,就会执行某个任务:

  • runAfterEither:组合两个任务,无入参,无返回值
  • acceptEither:组合两个任务,已经执行完成的任务作为方法入参,无返回值
  • applyToEither:组合两个任务,已经执行完成的任务作为方法入参,有返回值
  • anyOf:组合多个任务,返回值为Object
public static void main(String[] args) throws Exception {
    List<CompletableFuture<Integer>> futureList = new ArrayList<>();
    for (int i = 0; i < 4; i++) {
        int result = i + 1;
        futureList.add(CompletableFuture.supplyAsync(() -> {
            try {
                Thread.sleep(result * 1000);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            return result;
        }));
    }
    CompletableFuture<Object> future = CompletableFuture.anyOf(futureList.toArray(new CompletableFuture[0]));
    System.out.println(future.get());
}

// 输出
1