二、CompletableFuture(一)基础概念
一、基础
Future:Future接口定义了操作异步任务执行的方法,如获取任异步任务的执行结果、取消异步任务的执行、判断任务执行是否完毕等。
Callable:Callable接口定义了需要有返回结果的任务需要实现的方法。
二、Future的缺点。
阻塞。必须手动futureTask.get()查询执行结果,这样会造成阻塞,导致线程卡在get()方法处,等待返回结果,无法继续执行后续代码。
解决方法:
1、尽量使用带超时参数的get()方法。
2、isDone()轮询是否结束,再调用get()。
三、CompletableFuture
public class CompletableFuture<T> implements Future<T>, CompletionStage<T>
CompletionStage,代表异步计算过程中的某一个节点,一个阶段完成以后可以会触发另外一个阶段。
一个阶段的计算执行可以是一个Function、Consumer或者Runnable。
一个阶段的执行可能被单个阶段的完成触发,也可能时由多个阶段一起触发。
1、两种实现方法
public static void main(String[] args) { try { CompletableFuture<Void> future1 = CompletableFuture.runAsync(() -> { System.out.println("thread name :" + Thread.currentThread().getName()); }); System.out.println(future1.get()); CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> { System.out.println("thread name :" + Thread.currentThread().getName()); return 11; }); System.out.println(future2.get()); } catch (Exception e) { } }
runAsync:无返回值
supplyAsync:有返回值
如果runAsync和supplyAsync没有指定线程池,则使用默认线程池ForkJoinPool.commonPool()。
2、调用
public static void main(String[] args) { try { // 自定义线程池 ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(1, 20, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(50), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy()); CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { try { System.out.println(Thread.currentThread().getName() + "暂停3秒钟"); TimeUnit.SECONDS.sleep(3); } catch (InterruptedException e) { e.printStackTrace(); } return 5; }, threadPoolExecutor).thenApply(i -> { return i + 2; }).whenComplete((v, e) -> { if (e == null) { System.out.println("无异常,result:" + v); } }).exceptionally(e -> { e.printStackTrace(); return null; }); System.out.println("----- main -----"); // 延迟关闭主线程,否则CompletableFuture默认使用的线程池会立刻关闭。 // 当使用自定义线程池时,不用延长主线程。 // try { // System.out.println("主线程暂停5秒钟关闭"); // TimeUnit.SECONDS.sleep(5); // } catch (InterruptedException e) { // e.printStackTrace(); // } System.out.println("----- main over -----"); threadPoolExecutor.shutdown(); } catch (Exception e) { } }
3、优点
异步任务结束时,会自动回调某个对象的方法。
异步任务异常时,会自动回调某个对象的方法。
主线程设置好回调后,不用关心异步任务的执行,异步任务之间可以顺序执行,不会发生阻塞。
四、CompletableFuture Demo
public class CompletableFutureNetMailDemo { static List<Mall> list = Arrays.asList( new Mall("jd"), new Mall("pdd"), new Mall("taobao"), new Mall("suning"), new Mall("dangdang"), new Mall("tmail") ); /** * 同步 */ public static List<String> getPriceByStep(List<Mall> list, String productName) { return list.stream().map(mall -> String.format(productName + " in %s price is %.2f", mall.getMailName(), mall.calcPrice(productName))).collect(Collectors.toList()); } /** * 异步 */ public static List<String> getPriceBySync(List<Mall> list, String productName) { return list.stream() .map(mall -> CompletableFuture.supplyAsync(() -> String.format(productName + " in %s price is %.2f", mall.getMailName(), mall.calcPrice(productName)))) .collect(Collectors.toList()) .stream().map(CompletableFuture::join).collect(Collectors.toList()); } public static void main(String[] args) { long startTime1 = System.currentTimeMillis(); List<String> list1 = getPriceByStep(list, "test"); for (String element : list1) { System.out.println(element); } long endTime1 = System.currentTimeMillis(); System.out.println("--------- cost time:" + (endTime1 - startTime1) + "毫秒"); System.out.println(); long startTime2 = System.currentTimeMillis(); List<String> list2 = getPriceBySync(list, "test"); for (String element : list2) { System.out.println(element); } long endTime2 = System.currentTimeMillis(); System.out.println("--------- cost time:" + (endTime2 - startTime2) + "毫秒"); } } /** * 商城 */ @Data class Mall { private String mailName; public Mall(String mailName) { this.mailName = mailName; } /** * 随机价格 */ public double calcPrice(String productName) { // 检索1秒钟 try { //System.out.println(Thread.currentThread().getName() + "检索商城1秒钟"); TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } // 返回价格 return ThreadLocalRandom.current().nextDouble() * 2 + productName.charAt(0); } }