优雅的线程CompletableFuture线程
一、回顾一下Future。
@Test public void testFuture() throws ExecutionException, InterruptedException { ExecutorService executorService = Executors.newFixedThreadPool(5); Future<String> future = executorService.submit(() -> { Thread.sleep(5000); return "hello"; }); // 1 是异步输出,很快,不用等 System.out.println(1); // 这个 get 方法得五秒之后才会输出 System.out.println(future.get()); // end 要在五秒之后才会输出 System.out.println("end"); }
@Test public void testCountDownLatch() throws InterruptedException, ExecutionException { ExecutorService executorService = Executors.newFixedThreadPool(5); CountDownLatch downLatch = new CountDownLatch(2); long startTime = System.currentTimeMillis(); Future<String> userFuture = executorService.submit(() -> { // 模拟查询用户耗时500毫秒 Thread.sleep(500); downLatch.countDown(); return "用户A信息"; }); Future<String> goodsFuture = executorService.submit(() -> { // 模拟查询商品耗时500毫秒 Thread.sleep(400); downLatch.countDown(); return "商品A信息"; }); downLatch.await(); // 模拟主程序耗时时间 Thread.sleep(600); System.out.println("获取用户信息:" + userFuture.get()); System.out.println("获取商品信息:" + goodsFuture.get()); System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms"); }
输出:一共耗时1110ms
获取用户信息:用户A
获取商品信息:商品A
总共用时1110ms
从运行结果可以看出结果都已经获取,而且如果我们不用异步操作,执行时间应该是:500+400+600 = 1500,用异步操作后实际只用1110。
但是Java8以后我不在认为这是一种优雅的解决方式,接下来来了解下CompletableFuture的使用。
@Test public void testCompletableInfo() throws InterruptedException, ExecutionException { long startTime = System.currentTimeMillis(); // 调用用户服务获取用户基本信息 CompletableFuture<String> userFuture = CompletableFuture.supplyAsync(() -> { // 模拟查询用户耗时500毫秒 try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } return "用户A信息"; }); // 调用商品服务获取商品基本信息 CompletableFuture<String> goodsFuture = CompletableFuture.supplyAsync(() -> { // 模拟查询商品耗时400毫秒 try { Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } return "商品A"; }); System.out.println("获取用户信息:" + userFuture.get()); System.out.println("获取商品信息:" + goodsFuture.get()); // 模拟主程序耗时时间 Thread.sleep(600); System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms"); }
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier){..} public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor){..} public static CompletableFuture<Void> runAsync(Runnable runnable){..} public static CompletableFuture<Void> runAsync(Runnable runnable,Executor executor){..}
- 「supplyAsync」**异步执行任务,支持返回值。
- 「runAsync」**异步执行任务,没有返回值。
// 使用默认内置线程池ForkJoinPool.commonPool(),根据supplier构建执行任务 public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) // 自定义线程,根据supplier构建执行任务 public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier, Executor executor)
// 使用默认内置线程池ForkJoinPool.commonPool(),根据runnable构建执行任务 public static CompletableFuture<Void> runAsync(Runnable runnable) // 自定义线程,根据runnable构建执行任务 public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor)
2.结果获取的4种方式。
// 方式一 public T get() // 方式二 public T get(long timeout, TimeUnit unit) // 方式三 public T getNow(T valueIfAbsent) // 方式四 public T join()
- 「get() 和 get(long timeout, TimeUnit unit)」 => 在Future中就已经提供了,后者提供超时处理,如果在指定时间内未获取结果将抛出超时异常
- 「getNow」 => 立即获取结果不阻塞,结果计算已完成将返回结果或计算过程中的异常,如果未计算完成将返回设定的 valueIfAbsent 值
- 「join」 => 方法里不会抛出异常
@Test public void testCompletableGet() throws InterruptedException, ExecutionException { CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } return "商品A"; }); // getNow方法测试 System.out.println(cp1.getNow("商品B")); // join方法测试 CompletableFuture<Integer> cp2 = CompletableFuture.supplyAsync((() -> 1 / 0)); System.out.println(cp2.join()); System.out.println("-----------------------------------------------------"); // get方法测试 CompletableFuture<Integer> cp3 = CompletableFuture.supplyAsync((() -> 1 / 0)); System.out.println(cp3.get()); }
- 第一个执行结果为 「商品B」,因为要先睡上1秒结果不能立即获取
- join方法获取结果方法里不会抛异常,但是执行结果会抛异常,抛出的异常为CompletionException
- get方法获取结果方法里将抛出异常,执行结果抛出的异常为ExecutionException
3.异步回调方法
long startTime = System.currentTimeMillis(); CompletableFuture<Void> cp1 = CompletableFuture.runAsync(() -> { try { //执行任务A Thread.sleep(600); } catch (InterruptedException e) { e.printStackTrace(); } }); CompletableFuture<Void> cp2 = cp1.thenRun(() -> { try { //执行任务B Thread.sleep(400); } catch (InterruptedException e) { e.printStackTrace(); } }); // get方法测试 System.out.println(cp2.get()); // 模拟主程序耗时时间 Thread.sleep(600); System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms");
- 调用thenRun方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池。
- 调用thenRunAsync执行第二个任务时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池。
@Test public void testCompletableThenAccept() throws ExecutionException, InterruptedException { long startTime = System.currentTimeMillis(); CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> { return "dev"; }); CompletableFuture<Void> cp2 = cp1.thenAccept((a) -> { System.out.println("上一个任务的返回结果为: " + a); }); cp2.get(); }
c、thenApply / thenApplyAsync
CompletableFuture<String> cp1 = CompletableFuture.supplyAsync(() -> { return "dev"; }).thenApply((a) ->{ if (a.equals("dev")) { System.out.println("我是第二个返回结果:" + a); } return "b"; }); System.out.println(cp1.get());
// 输出
/**
* 我是第二个返回结果:dev
* b
**/
- 「正常完成」:whenComplete返回结果和上级任务一致,异常为null;
- 「出现异常」:whenComplete返回结果为null,异常为上级任务的异常;
throw new RuntimeException("出错了"); } System.out.println("正常结束"); return 0.11; }).whenComplete((aDouble, throwable) -> { if (aDouble == null) { System.out.println("whenComplete aDouble is null"); } else { System.out.println("whenComplete aDouble is " + aDouble); } if (throwable == null) { System.out.println("whenComplete throwable is null"); } else { System.out.println("whenComplete throwable is " + throwable.getMessage()); } }); System.out.println("最终返回的结果 = " + future.get());
// 正常输出
正常结束
whenComplete aDouble is 0.11
whenComplete throwable is null
最终返回的结果 = 0.11
// 异常输出
whenComplete aDouble is null
whenComplete throwable is java.lang.RuntimeException: 出错了
Exception in thread "main" java.util.concurrent.ExecutionException: java.lang.RuntimeException: 出错了
at java.base/java.util.concurrent.CompletableFuture.reportGet(CompletableFuture.java:395)
at java.base/java.util.concurrent.CompletableFuture.get(CompletableFuture.java:1999)
at main.java.com.xsy.Test.main(Test.java:55)
CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> { if (Math.random() < 0.5) { throw new RuntimeException("出错了"); } System.out.println("正常结束"); return 0.11; }).whenComplete((aDouble, throwable) -> { if (aDouble == null) { System.out.println("whenComplete aDouble is null"); } else { System.out.println("whenComplete aDouble is " + aDouble); } if (throwable == null) { System.out.println("whenComplete throwable is null"); } else { System.out.println("whenComplete throwable is " + throwable.getMessage()); } }).exceptionally((throwable -> { System.out.println("异常捕捉"); return 0.0; })); System.out.println("最终返回的结果 = " + future.get());
// 异常输出
whenComplete aDouble is null
whenComplete throwable is java.lang.RuntimeException: 出错了
异常捕捉
最终返回的结果 = 0.0
5.多任务组合回调
- 「runAfterBoth」 不会把执行结果当做方法入参,且没有返回值
- 「thenAcceptBoth」: 会将两个任务的执行结果作为方法入参,传递到指定方法中,且无返回值
- 「thenCombine」:会将两个任务的执行结果作为方法入参,传递到指定方法中,且有返回值
@Test public void testCompletableThenCombine() throws ExecutionException, InterruptedException { // 创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); // 开启异步任务1 CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> { System.out.println("异步任务1,当前线程是:" + Thread.currentThread().getId()); int result = 1 + 1; System.out.println("异步任务1结束"); return result; }, executorService); // 开启异步任务2 CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> { System.out.println("异步任务2,当前线程是:" + Thread.currentThread().getId()); int result = 1 + 1; System.out.println("异步任务2结束"); return result; }, executorService); // 任务组合 CompletableFuture<Integer> task3 = task.thenCombineAsync(task2, (f1, f2) -> { System.out.println("执行任务3,当前线程是:" + Thread.currentThread().getId()); System.out.println("任务1返回值:" + f1); System.out.println("任务2返回值:" + f2); return f1 + f2; }, executorService); Integer res = task3.get(); System.out.println("最终结果:" + res); }
运行结果
异步任务1,当前线程是:17 异步任务1结束 异步任务2,当前线程是:18 异步任务2结束 执行任务3,当前线程是:19 任务1返回值:2 任务2返回值:2 最终结果:4
- 「runAfterEither」:不会把执行结果当做方法入参,且没有返回值
- 「acceptEither」: 会将已经执行完成的任务,作为方法入参,传递到指定方法中,且无返回值
- 「applyToEither」:会将已经执行完成的任务,作为方法入参,传递到指定方法中,且有返回值
public void testCompletableEitherAsync() { // 创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); // 开启异步任务1 CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> { System.out.println("异步任务1,当前线程是:" + Thread.currentThread().getId()); int result = 1 + 1; System.out.println("异步任务1结束"); return result; }, executorService); // 开启异步任务2 CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> { System.out.println("异步任务2,当前线程是:" + Thread.currentThread().getId()); int result = 1 + 2; try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("异步任务2结束"); return result; }, executorService); // 任务组合 task.acceptEitherAsync(task2, (res) -> { System.out.println("执行任务3,当前线程是:" + Thread.currentThread().getId()); System.out.println("上一个任务的结果为:"+res); }, executorService); }
运行结果
运行结果 // 通过结果可以看出,异步任务2都没有执行结束,任务3获取的也是1的执行结果 异步任务1,当前线程是:17 异步任务1结束 异步任务2,当前线程是:18 执行任务3,当前线程是:19 上一个任务的结果为:2
ExecutorService executorService = Executors.newFixedThreadPool(1);
异步任务1,当前线程是:17
异步任务1结束
异步任务2,当前线程是:17
- 「allOf」:等待所有任务完成
- 「anyOf」:只要有一个任务完成
@Test public void testCompletableAallOf() throws ExecutionException, InterruptedException { // 创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); // 开启异步任务1 CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> { System.out.println("异步任务1,当前线程是:" + Thread.currentThread().getId()); int result = 1 + 1; System.out.println("异步任务1结束"); return result; }, executorService); // 开启异步任务2 CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> { System.out.println("异步任务2,当前线程是:" + Thread.currentThread().getId()); int result = 1 + 2; try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("异步任务2结束"); return result; }, executorService); // 开启异步任务3 CompletableFuture<Integer> task3 = CompletableFuture.supplyAsync(() -> { System.out.println("异步任务3,当前线程是:" + Thread.currentThread().getId()); int result = 1 + 3; try { Thread.sleep(4000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("异步任务3结束"); return result; }, executorService); // 任务组合 CompletableFuture<Void> allOf = CompletableFuture.allOf(task, task2, task3); // 等待所有任务完成 allOf.get(); // 获取任务的返回结果 System.out.println("task结果为:" + task.get()); System.out.println("task2结果为:" + task2.get()); System.out.println("task3结果为:" + task3.get()); }
anyOf: 只要有一个任务完成
@Test public void testCompletableAnyOf() throws ExecutionException, InterruptedException { // 创建线程池 ExecutorService executorService = Executors.newFixedThreadPool(10); // 开启异步任务1 CompletableFuture<Integer> task = CompletableFuture.supplyAsync(() -> { int result = 1 + 1; return result; }, executorService); // 开启异步任务2 CompletableFuture<Integer> task2 = CompletableFuture.supplyAsync(() -> { int result = 1 + 2; return result; }, executorService); // 开启异步任务3 CompletableFuture<Integer> task3 = CompletableFuture.supplyAsync(() -> { int result = 1 + 3; return result; }, executorService); // 任务组合 CompletableFuture<Object> anyOf = CompletableFuture.anyOf(task, task2, task3); // 只要有一个有任务完成 Object o = anyOf.get(); System.out.println("完成的任务的结果:" + o); }
@Test public void testWhenCompleteExceptionally() { CompletableFuture<Double> future = CompletableFuture.supplyAsync(() -> { if (1 == 1) { throw new RuntimeException("出错了"); } return 0.11; }); // 如果不加 get()方法这一行,看不到异常信息 // future.get(); }
// 反例 CompletableFuture.get(); // 正例 CompletableFuture.get(5, TimeUnit.SECONDS);
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· .NET10 - 预览版1新功能体验(一)