CompletableFuture 异步编程

本文详细介绍了Java8中的CompletableFuture,用于支持流式异步编程。文章讲解了如何创建任务,包括thenAccept()、thenApply()、thenCombine()、whenComplete()以及exceptionally()方法的使用,特别是它们在异常处理上的区别。此外,还探讨了ForkJoinPool在提高并行任务执行效率中的应用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CompletableFuture 异步编程

Java8 新增一个类 CompletableFuture,用来支持流式异步编程。

创建任务

CompletableFuture 提供一下API用来执行异步任务。

static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier);

static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor);

static CompletableFuture<Void> runAsync(Supplier<U> supplier);

static CompletableFuture<Void> runAsync(Supplier<U> supplier, Executor executor);

这些API都是用来创建任务,并且异步计算任务。supplyAsync 方法是有返回值的。例如:

CompletableFuture<String> futureA = CompletableFuture.supplyAsync(() -> "hello");

CompletableFuture<Void> futureB = CompletableFuture.supplyAsync(() -> "hello");
thenAccept()
CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action);

CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action, Executor executor);

表示当前这个阶段计算正常完成(没有异常发送),计算完成后的结果会作为 thenAcceptAsync 方法的入参,然后执行 action。如果当前阶段计算发送异常,thenAcceptAsync 方法不会被执行。

// 打印 hello
CompletableFuture.supplyAsync(() -> "hello")
               .thenAcceptAsync(result -> System.out.println(result));

// 不会打印. 因为不会执行thenAcceptAsync
CompletableFuture.supplyAsync(() -> {
                    throw new NullPointerException();
               })
               .thenAcceptAsync(rs -> System.out.println("1234"));
thenApply()
<U> CompletableFuture<U> thenApply(Function<? super T,? extends U> fn);

<U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn);

<U> CompletableFuture<U> thenApplyAsync(Function<? super T,? extends U> fn, Executor executor);

这个方法和 thenAcceptAsync 方法功能一样,就是返回值不同。

thenCombine()
<U,V> CompletableFuture<V>	thenCombine(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn);

<U,V> CompletableFuture<V>	thenCombineAsync(CompletionStage<? extends U> other, BiFunction<? super T,? super U,? extends V> fn);

这里表示当前阶段计算完成并且 other 计算阶段也完成(两个阶段都没有发生异常),然后把这两个阶段计算的结果,作为 thenCombineAsync 方法的入参,执行 fn。如果其中一个计算阶段发生异常,都不会执行该方法。

CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> "hello");

CompletableFuture<Long> taskB = CompletableFuture.supplyAsync(() -> 12L);
taskB.thenCombine(taskA, (resultB, resultA) -> {
    return resultA + resultB;
});
whenComplete()
CompletableFuture<T> whenComplete(BiConsumer<? super T,? super Throwable> action);

CompletableFuture<T> whenCompleteAsync(BiConsumer<? super T,? super Throwable> action);

这里表示无论当前阶段是计算完成还是发生异常,都将执行 whenComplete 方法。

CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> {
            return "hello";
        });

taskA.whenCompleteAsync((rs, ex) -> {
    if (ex != null) {
        ex.printStackTrace();
    }
    if (rs != null) {
        System.out.println(rs);
    }
});
exceptionally()
CompletableFuture<T> exceptionally(Function<Throwable,? extends T> fn);

这个方法也是用来处理异常的。只有在发生异常时才会执行该方法。

CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> {
            throw new IllegalArgumentException();
        });

taskA.exceptionally(ex -> {
    // 这里可以处理异常,然后返回补偿值, 而在 whenComplete() 方法只能知道有异常发送,不能去补偿。
    System.out.println(ex.getMessage());
    return "hello";
})
exceptionally() vs whenCompleteAsync()区别

调用顺序:先 exceptionally()whenCompleteAsync()

CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> {
            throw new IllegalArgumentException();
        });

taskA.exceptionally(ex -> {
    System.out.println(ex.getMessage());
    return "hello";
}).whenCompleteAsync((rs, ex) -> {
    if (ex != null) {
        ex.printStackTrace();
    }
    if (rs != null) {
        // IllegalArgumentException异常已经被exceptionally处理,并返回正常值 hello.
        // 这里没有异常,将打印 hello
        System.out.println(rs);
    }
});

在执行任务发送了异常 IllegalArgumentException,将执行方法 exceptionally ,然后执行方法 whenCompleteAsync

调用顺序:先 whenCompleteAsync()exceptionally()

CompletableFuture<String> taskA = CompletableFuture.supplyAsync(() -> {
            throw new IllegalArgumentException();
        });

taskA.whenCompleteAsync((rs, ex) -> {
    // ex 不为空,有 IllegalArgumentException 异常发送
    if (ex != null) {
        // 打印异常栈
        ex.printStackTrace();
    }
    if (rs != null) {
        System.out.println(rs);
    }
}).exceptionally(ex -> {
    // 捕获异常 IllegalArgumentException 并处理
    System.out.println(ex.getMessage());
    return "hello";
});
参考
拓展 ForkJoinPool

计算一些并行任务,可以使用 ForkJoinPool 来提供并行的方式提供资源利用率和吞吐量。这个类的大致原理:把大任务拆分成小任务,再去执行。每个空线程,也会去窃取其他繁忙线程的任务,帮忙处理。使用例子:

定义一个任务,继承 RecursiveAction 无返回值。需要有返回值的,继承RecursiveTask<T>

private static class FileUploadTask extends RecursiveAction{
        private int[] files;
        private static final int THRESHOLD = 20;

        public FileUploadTask(int[] files) {
            this.files = files;
        }

        @Override
        protected void compute() {
            if (files.length <= THRESHOLD) {
                this.upload();
            } else {
                System.out.println(Thread.currentThread().getName());
                FileUploadTask subTaskOne = new FileUploadTask(Arrays.copyOfRange(files, 0, files.length / 2));
                FileUploadTask subTaskTwo = new FileUploadTask(Arrays.copyOfRange(files, files.length / 2, files.length));
                subTaskOne.fork();
                subTaskTwo.fork();

                // 如果不关注返回值,可以考虑不需要这里
                subTaskTwo.join();  // 阻塞等待子任务计算完成,异常也会释放
                subTaskOne.join(); // 阻塞等待子任务计算完成
            }
        }

        private void upload()  {
            List<String> items = new ArrayList<>();
            HashSet<Integer> set = new HashSet<>();

            for (int item : files) {
                items.add(String.valueOf(item));
                set.add(item);
            }
            if (set.contains(1000)) {
                throw new IllegalArgumentException();
            }
            System.out.println("开始上传文件: " + String.join(",", items));
            try {
                Thread.sleep(200);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }

提交任务

public static void main(String[] args) throws Exception {
    int length = 100;
    int[] arr = new int[length];
    for (int i = 0; i < length; i++) {
        arr[i] = i + 1;
    }
    int parallelism = ForkJoinPool.commonPool().getParallelism();
    System.out.println("parallelism:" + parallelism);
    ForkJoinPool.commonPool().invoke(new FileUploadTask(arr));
    Thread.sleep(30000);
}
参考
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值