第九章:CompletableFuture组合式异步编程

9.1 Future 接口

计算从0到给定数值的自然数相加之和

public static Long addNums(long l) {
    long sum = LongStream.rangeClosed(0L, l)
                         .reduce(0L, Long::sum);
    return sum;
}

同步执行

public static Long testSyn(long l) {
    for (int i = 0; i < 500; i++) {
        addNums(l);
    }
    Long addNums = addNums(l);
    return addNums;
}

异步执行

public static Long testAsyn(long l) {        
    try {
        // 创建ExecutorService,通过它你可以向线程池提交任务
        ExecutorService executor = Executors.newFixedThreadPool(4);
        List<Callable<Long>> tasks = new ArrayList<Callable<Long>>();
        for (int i = 0; i < 500; i++) {
            tasks.add(new Callable<Long>() {
                @Override
                public Long call() throws Exception {
                    return addNums(l);
                }
            });
        }
        // 异步执行
        List<Future<Long>> invokeAll = executor.invokeAll(tasks);
        // 异步操作的同时执行其他操作
        addNums(l);
        // 获取异步操作的结果,如果被阻塞无法得到结果,那么在最多等待1秒之后退出
        return invokeAll.get(0).get(1, TimeUnit.SECONDS);
    // 当前线程在等待中被中断跑车不异常
    } catch (InterruptedException e) {
        e.printStackTrace();
    // 计算抛出异常
    } catch (ExecutionException e) {
        e.printStackTrace();
    // 在Future对象完成之前超时抛出异常
    } catch (TimeoutException e) {
        e.printStackTrace();
    }
    return null;
}

测试

public static void main(String[] args) {
        
    // 测试异步与同步
    long l1 = System.currentTimeMillis();
    Long testAsyn = testAsyn(1000000);
    System.out.println("testAsyn: " + (System.currentTimeMillis() - l1) + " ms, " + testAsyn);
    l1 = System.currentTimeMillis();
    Long testSyn = testSyn(1000000);
    System.out.println("testSyn: " + (System.currentTimeMillis() - l1) + " ms, " + testSyn);
        
}

 

9.2 使用CompletableFuture实现异步API

商品类 Shop

private String name;
public String getName() {
    return name;
}

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

public Shop() {
    super();
}

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

// 获取商品价格的方法
public double getPrice(String product) {
    return calculatePrice(product);
}
    
// 模拟1秒延迟的方法
public static void delay() {
    try {
        Thread.sleep(1000);
    } catch (Exception e) {
        e.printStackTrace();
    }
}
    
// 计算商品价格的方法
public double calculatePrice(String product) {
    delay();
    return new Random().nextDouble() * product.charAt(0) + product.charAt(1);
}

9.2.1 实现异步API

public Future<Double> getPriceAsyn(String product){
    // 创建 CompletableFuture 对象
    CompletableFuture<Double> future = new CompletableFuture<Double>();
    // 启动一个线程执行异步计算
    new Thread(() -> {
        double price = calculatePrice(product);
        future.complete(price);
    }).start();
        
    return future;
}

测试

   // 使用CompletableFuture异步API
    long l1 = System.currentTimeMillis();
    Future<Double> future = new Shop().getPriceAsyn("ab");
    System.out.println("return future : " + (System.currentTimeMillis() - l1) + " ms.");
    // 执行其他更多的任务
    // doSomething
    // 从Future中获取商品价格,如果价格未知,会发生阻塞
    try {
        Double price = future.get();
        System.out.println("return price : " + String.format("%.2f", price) + " --> " + (System.currentTimeMillis() - l1) + " ms.");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }

9.2.2 CompletableFuture异常处理方法completeExceptionally

public Future<Double> getPriceAsynException(String product){
    // 创建 CompletableFuture 对象
    CompletableFuture<Double> future = new CompletableFuture<Double>();
    // 启动一个线程执行异步计算
    new Thread(() -> {
        try {
            double price = calculatePrice(product);
            // 如果价格计算正确结束,完成Future操作并设置商品价格
            future.complete(price);
        } catch (Exception e) {
            // 否则就抛出导致失败的异常,完成这次Future操作
            future.completeExceptionally(e);
        }
    }).start();
        
    return future;
}

9.2.3 使用工厂方法supplyAsync创建CompletableFuture

public Future<Double> getPriceSupplyAsync(String product){
    return CompletableFuture.supplyAsync(() -> calculatePrice(product));
}

测试

    // 使用CompletableFuture异步API
    long l1 = System.currentTimeMillis();
    Future<Double> future = new Shop().getPriceSupplyAsync("ab");
    System.out.println("return future : " + (System.currentTimeMillis() - l1) + " ms.");
    // 执行其他更多的任务
    // doSomething
    // 从Future中获取商品价格,如果价格未知,会发生阻塞
    try {
        Double price = future.get();
        System.out.println("return price : " + String.format("%.2f", price) + " --> " + (System.currentTimeMillis() - l1) + " ms.");
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (ExecutionException e) {
        e.printStackTrace();
    }

9.3 让你的代码免受阻塞

9.3.1 对比顺序流与并行流

构造数据--多个商品

List<Shop> shops = Arrays.asList(new Shop("ab"),
                                 new Shop("qw"),
                                 new Shop("er"),
                                 new Shop("as"),
                                 new Shop("zx"));

  顺序流

public static List<String> findPrice(List<Shop> shops) {
    return shops.stream()
                .map(shop -> {
                    try {
                        return String.format("%s 价格: %.2f", shop.getName(), shop.getPriceSupplyAsync(shop.getName()).get());
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    } catch (ExecutionException e) {
                        e.printStackTrace();
                    }
                    return null;
                })
                .collect(Collectors.toList());
}

  并行流

public static List<String> findPriceParallel(List<Shop> shops) {
    return shops.parallelStream()
            .map(shop -> {
                try {
                    return String.format("%s 价格: %.2f", shop.getName(), shop.getPriceSupplyAsync(shop.getName()).get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                return null;
            })
            .collect(Collectors.toList());
}

测试

// 顺序流与并行流
    long l1 = System.currentTimeMillis();
    List<String> findPrice = findPrice(shops);
    System.out.println("findPrice: " + findPrice + "\n--> " + (System.currentTimeMillis() - l1) + " ms.");
    l1 = System.currentTimeMillis();
    List<String> findPriceParallel = findPriceParallel(shops);
    System.out.println("findPriceParallel: " + findPriceParallel + "\n--> " + (System.currentTimeMillis() - l1) + " ms.");

结果

findPrice: [ab 价格: 158.00, qw 价格: 187.62, er 价格: 176.11, as 价格: 115.17, zx 价格: 131.72]
--> 5017 ms.
findPriceParallel: [ab 价格: 114.67, qw 价格: 126.58, er 价格: 150.21, as 价格: 136.07, zx 价格: 147.93]
--> 4011 ms.

分析

并行流比顺序流执行效率高

 

9.3.2 使用CompletableFuture发起异步请求

public static List<String> findPriceFuture(List<Shop> shops) {
    return shops.stream()
             .map(shop -> CompletableFuture.supplyAsync(() -> {
                try {
                    return String.format("%s 价格: %.2f", shop.getName(), shop.getPriceSupplyAsync(shop.getName()).get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                return null;  
                })).map(CompletableFuture::join).collect(Collectors.toList());
}

备注: CompletableFuture类的join方法和Future的get方法有相同的含义,并且也声明在Future接口中,它们唯一的不同就是join不会抛出任何检测到的异常

测试

long l1 = System.currentTimeMillis();
List<String> findPriceFuture = findPriceFuture(shops);
System.out.println("findPriceFuture: " + findPriceFuture + "\n--> " + (System.currentTimeMillis() - l1) + " ms.");

结果

findPriceFuture: [ab 价格: 161.14, qw 价格: 198.34, er 价格: 130.01, as 价格: 150.33, zx 价格: 218.52]
--> 5005 ms.

分析

流操作之间有延迟特性,如果你在单一流水线中处理流,发向不同商家的请求只能以同步、顺序执行的方式才能成功。因此每个创建CompletableFuture对象只能在前一个操作结束之后执行接下来的动作并通知join方法返回计算结果。

优化:将一个流水线变成两个流水线,使CompletableFuture对象的返回为异步执行,等所有的CompletableFuture对象返回完成后一起处理

public static List<String> findPriceBestFuture(List<Shop> shops) {
    List<CompletableFuture<String>> priceFutures = shops.stream()
            .map(shop -> CompletableFuture.supplyAsync(() -> {
                try {
                    return String.format("%s 价格: %.2f", shop.getName(), shop.getPriceSupplyAsync(shop.getName()).get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                return null;
            })).collect(Collectors.toList());
    return priceFutures.stream()
                       .map(CompletableFuture::join)
                       .collect(Collectors.toList());
}

测试

long l1 = System.currentTimeMillis();
List<String> findPriceBestFuture = findPriceBestFuture(shops);
System.out.println("findPriceBestFuture: " + findPriceBestFuture + "\n--> " + (System.currentTimeMillis() - l1) + " ms.");

结果

findPriceBestFuture: [ab 价格: 145.24, qw 价格: 132.90, er 价格: 139.23, as 价格: 153.32, zx 价格: 180.89]
--> 2003 ms.

分析

异步执行效率明显提高

9.3.3 使用定制的执行器

  指定你要创建的线程数,根据机器的配置与性能创建最佳的线程数以达到最大的执行效率。线程数创建过多反而是一种浪费,导致程序执行效率降低。这里指定线程数为5个

public static List<String> findPriceExecutorFuture(List<Shop> shops) {
    final ExecutorService executor = Executors.newFixedThreadPool(5);
    List<CompletableFuture<String>> priceFutures = shops.stream()
            .map(shop -> CompletableFuture.supplyAsync(() -> {
                try {
                    return String.format("%s 价格: %.2f", shop.getName(), shop.getPriceSupplyAsync(shop.getName()).get());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (ExecutionException e) {
                    e.printStackTrace();
                }
                return null;
            }, executor)).collect(Collectors.toList());
    return priceFutures.stream()
                       .map(CompletableFuture::join)
                       .collect(Collectors.toList());
}

测试

long l1 = System.currentTimeMillis();
List<String> findPriceExecutorFuture = findPriceExecutorFuture(shops);
System.out.println("findPriceExecutorFuture: " + findPriceExecutorFuture + "\n--> " + (System.currentTimeMillis() - l1) + " ms.");

结果

findPriceExecutorFuture: [ab 价格: 165.42, qw 价格: 132.56, er 价格: 132.27, as 价格: 200.77, zx 价格: 205.87]
--> 2024 ms.

分析

执行效率与非定制异步执行效率差一点(笔记本配置有限)

 

 

备注:

摘自文献:《Java8实战》(中文版)《Java8 in Action》(英文版)

 

代码(GitHub地址): https://github.com/changlezhong/java8InAction

 

posted on 2018-06-17 16:26  changlezhong  阅读(385)  评论(0)    收藏  举报

导航