CompletableFuture异步调用使用技巧

一、并发与并行

  异步,陌生而熟悉的词汇,做开发的都知道

          

二、java1.5的Future接口

  Future接口在Java 5中被引入,设计初衷是对将来某个时刻会发生的结果进行建模。它建模了一种异步计算,返回一个执行运算结果的引用,当运算结束后,这个引用被返回给调用方。在Future中触发那些潜在耗时的操作把调用线程解放出来,让它能继续执行其他有价值的工作,不再需要呆呆等待耗时的操作完成。打个比方,你可以把它想象成这样的场景:你拿了一袋子衣服到你中意的干洗店去洗。干洗店的员工会给你张发票,告诉你什么时候你的衣服会洗好(这就是一个Future事件)。衣服干洗的同时,你可以去做其他的事情。Future的另一个优点是它比更底层的Thread更易用。要使用Future,通常你只需要将耗时的操作封装在一个Callable对象中,再将它提交给ExecutorService,就万事大吉了。下面这段代码展示了Java 8之前使用Future的一个例子。

ExecutorService executor = Executors.newCachedThreadPool();
    Future<Double> future = executor.submit(new Callable<Double>() {
        public Double call() {
            return doSomeLongComputation();
        }});
    doSomethingElse();
    try {
        Double result = future.get(1, TimeUnit.SECONDS);
    } catch (ExecutionException ee) {
// 计算抛出一个异常
    } catch (InterruptedException ie) {
// 当前线程在等待过程中被中断
    } catch (TimeoutException te) {
// 在Future对象完成之前超过已过期
    }

Future的局限性 

  通过第一个例子,我们知道Future接口提供了方法来检测异步计算是否已经结束(使用isDone方法),等待异步操作结束,以及获取计算的结果。但是这些特性还不足以让你编写简洁的并发代码。比如,我们很难表述Future结果之间的依赖性;从文字描述上这很简单,“当长时间计算任务完成时,请将该计算的结果通知到另一个长时间运行的计算任务,这两个计算任务都完成后,将计算的结果与另一个查询操作结果合并”。但是,使用Future中提供的方法完成这样的操作又是另外一回事。这也是我们需要更具描述能力的特性的原因,比如下面这些。
1、 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第
一个的结果。
 2、等待Future集合中的所有任务都完成。
 3、仅等待Future集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同
一个值),并返回它的结果。
 4、通过编程方式完成一个Future任务的执行(即以手工设定异步操作结果的方式)。
 5、应对Future的完成事件(即当Future的完成事件发生时会收到通知,并能使用Future计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)。

三、强大的CompletableFuture

  废话不说,直接上代码

public class Shop {

    public Future<Double> getPriceAsync(String product){
     // 创建CompletableFuture对象,他会包含计算结果 CompletableFuture
<Double> futurePrice = new CompletableFuture<>();
     // 在另一个线程中异步执行
new Thread(() -> { double price = calculatePrice(product);
       // 需长时间计算的任务结果并得出结果时,设置Future的返回值 futurePrice.complete(price); }).start();
     // 无需等待还没结束的计算,直接返回结果
return futurePrice; } private double calculatePrice(String product){ delay(); return new Random().nextDouble() * product.charAt(0) + product.charAt(1); } public static void delay(){ try { TimeUnit.SECONDS.sleep(2); }catch (InterruptedException e){ } } public static void main(String[] args) { Shop shop = new Shop(); long startTime = System.currentTimeMillis(); Future<Double> priceAsync = shop.getPriceAsync("my favorite product"); long invocationTime = ((System.currentTimeMillis() - startTime) / 1_000_000); System.out.println("invocation return after " + invocationTime + "msecs"); delay(); try { double price = priceAsync.get(); System.out.println("price is %.2f&n" + price); }catch (Exception e){ throw new RuntimeException(e); } } }

错误处理:

如果没有意外,我们目前开发的代码工作得很正常。但是,如果价格计算过程中产生了错误会怎样呢?非常不幸,这种情况下你会得到一个相当糟糕的结果:用于提示错误的异常会被限制在试图计算商品价格的当前线程的范围内,最终会杀死该线程,而这会导致等待get方法返回结果的客户端永久地被阻塞。客户端可以使用重载版本的get方法,它使用一个超时参数来避免发生这样的情况。这是一种值得推荐的做法,你应该尽量在你的代码中添加超时判断的逻辑,避免发生类似的问题。使用这种方法至少能防止程序永久地等待下去,超时发生时,程序会得到通知发生了Timeout-Exception。不过,也因为如此,你不会有机会发现计算商品价格的线程内到底发生了什么问题才引发了这样的失效。为了让客户端能了解商店无法提供请求商品价格的原因,你需要使用CompletableFuture的completeExcep-tionally方法将导致CompletableFuture内发生问题的异常抛出。对代码清单11-4优化后的结果如下所示。

public Future<Double> getPriceAsync(String product) {
    CompletableFuture<Double> futurePrice = new CompletableFuture<>();
    new Thread( () -> {
        try {
            double price = calculatePrice(product);
            futurePrice.complete(price);
        } catch (Exception ex) {
       // 将异常包装返回 futurePrice.completeExceptionally(ex); } }).start();
return futurePrice; }

猜你喜欢

转载自www.cnblogs.com/huanglog/p/10718390.html