Java8新的异步编程 CompletableFuture

645 阅读5分钟

1. Future

Future模式是从JDK5引入的,Future接口是其的实现。Future实现在JUC包中,用来进行异步计算意图简化并发计算的开发工作量。

Future的接口提供了五个方法。

public interface Future<V> {
    
    //取消任务的执行。参数指定是否立即中断任务执行,或者等等任务结束
    boolean cancel(boolean mayInterruptIfRunning);
    
    //判断任务是否已经取消,任务正常完成前将其取消,则返回 true
    boolean isCancelled();
    
    //判断任务是否已经完成。需要注意的是如果任务正常终止、异常或取消,都将返回true
    boolean isDone();
    
    //等待任务执行结束,然后获得V类型的结果。
    V get() throws InterruptedException, ExecutionException;

    //获得计算结果,加入了超时时间设置
    V get(long timeout, TimeUnit unit)
        throws InterruptedException, ExecutionException, TimeoutException;
}

Future工作原理如图所示。任务提交给Future后,Future替我完成这个任务。期间我可以去做其他事情,计算完成后从Future那取出整合结果即可。

image.png

我们这边举一个简单的例子演示一下Future的合并结果的功能。假设:计算累加求和。可以将这个问题分解为求和两个子计算。这两个子计算可以并行处理,最后再把两个子计算的结果合并。理论上,计算时间可以节省一半左右。

    Future<Integer> future1 = executor.submit(()->1+2);
    Future<Integer> future2 = executor.submit(()->3+4);
    int result = future1.get()+future2.get();

Future的局限性: Future只提供了一种获取计算结果的方法,那就是get(),这是一个阻塞方法。 所以Future适用于将一个任务分解为多个不相关的、并行处理的子任务。最后采用阻塞的方式,从所有子任务获取计算结果再汇总。

2. CompletableFuture

CompletableFuture是Future的子类。它除了get()方法外,还定义了诸多异步方式传递操作结果的方法。如果说普通Future的适用于拆分任务,减少计算时间。那么CompletableFuture适用于:协调组织NIO、异步消息的交互过程,减少线程资源占用。

  • 在Java8中,CompletableFuture提供了非常强大的Future的扩展功能,可以帮助我们简化异步编程的复杂性,并且提供了函数式编程的能力,可以通过回调的方式处理计算结果,也提供了转换和组合 CompletableFuture 的方法。

  • 它可能代表一个明确完成的Future,也有可能代表一个完成阶段( CompletionStage ),它支持在计算完成以后触发一些函数或执行某些动作。

  • 它实现了Future和CompletionStage接口。 在这里插入图片描述

CompletableFuture——小窍门

  • 所有thenXXX都只能处理正确的结果。whenComplete和handler可以处正确和错误的结果

  • 如果方法名中有accpet,例如thenAccept。那么表示只能接收结果,不能返回新的结果

  • 如果方法名中有apply,例如thenApply。那么表示可以返回新的结果

如果方法名中有run,例如thenRun。表示即不能接收结果,也不能返回结果。只是在合适的时机被触发。

实例演示

为了实现最佳价格查询器应用,让我们从每个商店都应该提供的API定义入手。首先,商店应该声明依据指定产品名称返回价格的方法。同时我们查询价格的方法实现了阻塞和异步来进行对比。

package com.lyc.completablefuture;

import java.util.concurrent.CompletableFuture;

/**
 * <p>文件名称:Shop</p>
 * <p>文件描述:每个商店都提供的对外访问的API </p>
 * <p>完成日期:2021年07年12日 17:30:58</p>
 *
 * @author luoyongchang
 */
public class Shop {

    /**
     * 商店名称
     */
    private String name;


    public Shop(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    /**
     * (阻塞式) 通过名称查询价格
     *
     * @param product
     * @return
     */
    public double getPrice(String product) {
        return calculatePrice(product);
    }

    /**
     * 计算价格
     *
     * @param product
     * @return
     */
    private double calculatePrice(String product) {
        delay();
        return 10 * product.charAt(0);
    }

    /**
     * 模拟耗时操作,阻塞1秒
     */
    private void delay() {
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    /**
     * 使用静态工厂supplyAsync(非阻塞式)异步获取价格
     *
     * @param product
     * @return
     */
    public CompletableFuture<Double> getPriceAsync(String product) {

        //无需等待还没结束的计算,直接返回Future对象
        return CompletableFuture.supplyAsync(() -> calculatePrice(product));
    }

}

接下来我们开始编写测试类调用上面的API服务实现我们的商品比价器:

package com.lyc.completablefuture;

import org.junit.Test;

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

/**
 * <p>文件名称:FutureTestDemo</p>
 * <p>文件描述:测试类 </p>
 * <p>完成日期:2021年07年12日 17:12:14</p>
 *
 * @author luoyongchang
 */
public class FutureTestDemo {
    /**
     * 测试同步API
     */
    @Test
    public void testGetPrice(){
        Shop shop = new Shop("Best Shop");
        long start = System.nanoTime();
        double price = shop.getPrice("mac book pro");
        System.out.printf(shop.getName()+" Price is %.2f%n",price);
        long invocationTime = (System.nanoTime()-start)/1_000_000;
        System.out.println("同步方法调用花费时间:--- "+invocationTime+" --- msecs");


        //...其他操作
        doSomethingElse();


        long retrievalTime = (System.nanoTime()-start)/1_000_000;
        System.out.println("同步方法返回价格所需时间: --- "+retrievalTime+" ---msecs");

    }


    /**
     * 测试异步API
     */
    @Test
    public void testAsync(){
        Shop shop = new Shop("Best Shop");
        long start = System.nanoTime();
        CompletableFuture<Double> futurePrice = shop.getPriceAsync("mac book pro");
        long invocationTime = (System.nanoTime()-start)/1_000_000;
        System.out.println("异步方法调用花费时间: --- "+invocationTime+" --- msecs");


        //...其他操作
        doSomethingElse();

        //从future对象中读取价格,如果价格未知,则发生阻塞.
        try {
            Double price = futurePrice.get();
            System.out.printf(shop.getName()+" Price is %.2f%n",price);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }

        long retrievalTime = (System.nanoTime()-start)/1_000_000;
        System.out.println("异步方法返回价格所需时间: --- "+retrievalTime+" ---msecs");

    }




    /**
     * 其它操作
     */
    public static void doSomethingElse(){
        try {
            Thread.sleep(1_000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}

测试结果: 在这里插入图片描述

总结:可以明显看到使用CompletableFuture来实现异步编程,与传统的同步相比可以大幅度提高运行速度。