第九章: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) 收藏 举报
浙公网安备 33010602011771号