Java 8 CompletableFuture API使用教程

JDK8引入了大量的新特性和增强如Lambda表达式,Streams,CompletableFuture等。本篇文章结合代码案例讲解下CompletableFuture常见用法。

什么是CompletableFuture?

在Java中CompletableFuture用于异步编程,异步编程是编写非阻塞的代码,运行的任务在一个单独的线程,与主线程隔离,并且会通知主线程它的进度,成功或者失败。

在这种方式中,主线程不会被阻塞,不需要一直等到子线程完成。主线程可以并行的执行其他任务。

使用这种并行方式,可以极大的提高程序的性能。

CompletableFuture相对于传统的Future的优势

        1. 可以手动完成调用future.complete()

        2.多个 Future 可以串联在一起组成链式调用 执行一个长时间运行的计算任务,并且当计算任务完成的时候,你需要把它的计算结果发送给另外一个长时间运行的计算任务等等

        3.Future可以在非阻塞的情况下执行进一步的操作,利用thenApply(),thenAccept() 和 thenRun() API 可以实现。

        4.可以组合多个Future结果,使用API:allof(),anyof(),thenCombine(),thenCompose()

        5.增加了异常处理API,没有任务的异常处理结构有很多的限制。

1.get() 阻塞获得CompletableFuture的结果;get() 方法会一直阻塞直到 Future 完成。因此,以上的调用将被永远阻塞,因为该Future一直不会完成。

2.runAsync()用于运行异步计算,通常异步运行一个后台任务不需要该任务的返回结果。(注意:这里的Runnable对象采用的是Lambda表达式)

    //runAsync() 运行异步计算 异步运行一个后台任务不需要该任务的返回结果
    @Test
    public void test01() throws ExecutionException, InterruptedException {
        CompletableFuture completableFuture = new CompletableFuture<String>();
        completableFuture.complete("finished future");
        completableFuture.get(); // get 方法会阻塞直到future完成
        System.out.println("completableFuture finished");
        //Using Lambda expression
        CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            System.out.println("在独立的子线程运行,非主线程");
        });

        future.get();

    }

3.supplyAsync()用于异步运行并返回结果。当运行异步线程你需要依赖它的返回结果时,就使用该方法。

        @Test
        public void test02() throws ExecutionException, InterruptedException {
            CompletableFuture<String> future = CompletableFuture.supplyAsync(new Supplier<String>() {
                @Override
                public String get() {
                    //模拟耗时的计算线程
                    try {
                        TimeUnit.SECONDS.sleep(5);
                    } catch (InterruptedException e) {
                        throw new RuntimeException(e);
                    }
                    return "Result of the asynchronous computation";// 返回异步计算结果
                }
            });

            String result = future.get(); // 阻塞等待线程结束返回结果
            System.out.println("result====>" + result);

        }

CompletableFuture的转化和组合

4.thenApply() 可以处理和改变CompletableFuture的结果 ,thenAccept() 和 thenRun() 如果你不想从你的回调函数中返回任何东西,仅仅想在Future完成后运行一些代码片段。(如下图)

5.CompletableFuture.allOf() CompletableFuture.allOf的使用场景是当你一个列表的独立future,并且你想在它们都完成后并行的做一些事情。可以组合多个CompletableFuture的结果集;最常用的场景是你需要异步处理一个List,并合并处理后的结果集。

        //场景: 多线程异步处理List,并合并结果集
        @Test
        public void test03(){
            
            //模拟数据
            List<String> dataList = new ArrayList<>();
            for(int i=0;i<100;i++){
                dataList.add("Data" + i);
            }

            List<CompletableFuture<List<String>>> futures = new ArrayList<>();
            // 每个CompletableFuture负责查询一部分数据
            int batchSize = 20;
            for (int i = 0; i < dataList.size(); i += batchSize) {
                int endIndex = Math.min(i + batchSize, dataList.size());
                List<String> subList = dataList.subList(i, endIndex);
                CompletableFuture<List<String>> future = CompletableFuture.supplyAsync(() -> performQuery(subList));
                futures.add(future);
            }
            //使用allof方法等待所有查询完成
            CompletableFuture<Void> allfutures = CompletableFuture.allOf(futures.toArray(new CompletableFuture[0]));
            //合并结果集
            // thenApply() 可以使用 thenApply() 处理和改变CompletableFuture的结果
            //thenAccept() 和 thenRun() 如果你不想从你的回调函数中返回任何东西,仅仅想在Future完成后运行一些代码片段
            CompletableFuture<List<String>> listCompletableFuture = allfutures.thenApply(v -> futures.stream().map(CompletableFuture::join).flatMap(List::stream).collect(Collectors.toList()));
            //阻塞等待合并结果
            List<String> result = listCompletableFuture.join();

            for (String s: result) {
                System.out.println("result====>" + s);
            }

        }

    private List<String> performQuery(List<String> subList) {
    // 模拟查询耗时
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        // 返回查询结果
        List<String> result = new ArrayList<>();
        for (String item : subList) {
            result.add("Result for " + item);
        }
        return result;

    }

值得一提的是,我们在处理CompletableFuture结果集时,当所有future完成的时候,我们调用了future.join(),因此我们不会在任何地方阻塞

6.CompletableFuture.anyOf()

CompletableFuture.anyOf()和其名字介绍的一样,当任何一个CompletableFuture完成的时候【相同的结果类型】,返回一个新的CompletableFuture

        @Test
        public void test05() throws ExecutionException, InterruptedException {
            CompletableFuture<String> future1 = CompletableFuture.supplyAsync(() -> {
                try {
                    TimeUnit.SECONDS.sleep(2);
                } catch (InterruptedException e) {
                    throw new IllegalStateException(e);
                }
                return "Result of Future 1";
            });

            CompletableFuture<String> future2 = CompletableFuture.supplyAsync(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new IllegalStateException(e);
                }
                return "Result of Future 2";
            });

            CompletableFuture<String> future3 = CompletableFuture.supplyAsync(() -> {
                try {
                    TimeUnit.SECONDS.sleep(3);
                } catch (InterruptedException e) {
                    throw new IllegalStateException(e);
                }
                return "Result of Future 3";
            });
            CompletableFuture<Object> anyOfFuture = CompletableFuture.anyOf(future1, future2, future3);
            System.out.println(anyOfFuture.get()); // Result of Future 2

        }

在以上示例中,当三个中的任何一个CompletableFuture完成, anyOfFuture就会完成。因为future2的休眠时间最少,因此她最先完成,最终的结果将是future2的结果。

7.使用thenCombine()组合两个独立的 future。另外你想从CompletableFuture链中获取一个直接合并后的结果,这时候你可以使用thenCompose()

 @Test
        public void test04() throws ExecutionException, InterruptedException {
            System.out.println("Retrieving weight.");
            CompletableFuture<Double> weightInKgFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new IllegalStateException(e);
                }
                return 72.0;
            });

            System.out.println("Retrieving height.");
            CompletableFuture<Double> heightInCmFuture = CompletableFuture.supplyAsync(() -> {
                try {
                    TimeUnit.SECONDS.sleep(1);
                } catch (InterruptedException e) {
                    throw new IllegalStateException(e);
                }
                return 173.5;
            });

            System.out.println("Calculating BMI.");
            CompletableFuture<Double> combinedFuture = weightInKgFuture
                    .thenCombine(heightInCmFuture, (weightInKg, heightInCm) -> {
                        Double heightInMeter = heightInCm/100;
                        return weightInKg/(heightInMeter*heightInMeter);
                    });

            System.out.println("Your BMI is - " + combinedFuture.get());

        }

CompletableFuture 异常处理

我们探寻了怎样创建CompletableFuture,转换它们,并组合多个CompletableFuture。现在让我们弄明白当发生错误的时候我们应该怎么做。

首先让我们明白在一个回调链中错误是怎么传递的。思考下以下回调链:

CompletableFuture.supplyAsync(() -> {
	// Code which might throw an exception
	return "Some result";
}).thenApply(result -> {
	return "processed result";
}).thenApply(result -> {
	return "result after further processing";
}).thenAccept(result -> {
	// do something with the final result
});

如果在原始的supplyAsync()任务中发生一个错误,这时候没有任何thenApply会被调用并且future将以一个异常结束。如果在第一个thenApply发生错误,这时候第二个和第三个将不会被调用,同样的,future将以异常结束。

1. 使用 exceptionally() 回调处理异常 exceptionally()回调给你一个从原始Future中生成的错误恢复的机会。你可以在这里记录这个异常并返回一个默认值。

Integer age = -1;

CompletableFuture<String> maturityFuture = CompletableFuture.supplyAsync(() -> {
    if(age < 0) {
        throw new IllegalArgumentException("Age can not be negative");
    }
    if(age > 18) {
        return "Adult";
    } else {
        return "Child";
    }
}).exceptionally(ex -> {
    System.out.println("Oops! We have an exception - " + ex.getMessage());
    return "Unknown!";
});

System.out.println("Maturity : " + maturityFuture.get()); 

2. 使用 handle() 方法处理异常 API提供了一个更通用的方法 - handle()从异常恢复,无论一个异常是否发生它都会被调用。

Integer age = -1;

CompletableFuture<String> maturityFuture = CompletableFuture.supplyAsync(() -> {
    if(age < 0) {
        throw new IllegalArgumentException("Age can not be negative");
    }
    if(age > 18) {
        return "Adult";
    } else {
        return "Child";
    }
}).handle((res, ex) -> {
    if(ex != null) {
        System.out.println("Oops! We have an exception - " + ex.getMessage());
        return "Unknown!";
    }
    return res;
});

System.out.println("Maturity : " + maturityFuture.get());

如果异常发生,res参数将是 null,否则,ex将是 null。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值