来源 | 捡田螺的小男孩(ID:gh_51e0e901a289)
。前言
大家好,我是田螺。
日常开发中,我们经常喜欢用CompletableFuture。但是它在使用的过程中,容易忽略几个坑,今天田螺哥给大家盘点一下~~
CompletableFuture使用的优点
既然上来说CompletableFuture可能隐藏几个坑,那为什么我们还要使用它呢?
CompletableFuture 是 Java 8 引入的异步编程工具,它的核心优势在于简化异步任务编排、提升代码可读性和灵活性。
我们来看一个使用CompletableFuture的例子吧,代码如下:
假设我们有两个任务服务,一个查询用户基本信息,一个是查询用户勋章信息。
public class FutureTest { public static void main(String[] args) throws InterruptedException, ExecutionException, TimeoutException { UserInfoService userInfoService = new UserInfoService(); MedalService medalService = new MedalService(); long userId =666L; long startTime = System.currentTimeMillis(); //调用用户服务获取用户基本信息 CompletableFuture<UserInfo> completableUserInfoFuture = CompletableFuture.supplyAsync(() -> userInfoService.getUserInfo(userId)); Thread.sleep(300); //模拟主线程其它操作耗时 CompletableFuture<MedalInfo> completableMedalInfoFuture = CompletableFuture.supplyAsync(() -> medalService.getMedalInfo(userId)); UserInfo userInfo = completableUserInfoFuture.get(2,TimeUnit.SECONDS);//获取个人信息结果 MedalInfo medalInfo = completableMedalInfoFuture.get();//获取勋章信息结果 System.out.println("总共用时" + (System.currentTimeMillis() - startTime) + "ms"); }}
其实,简简单单就能实现异步效果,可以说使用CompletableFuture很丝滑了。接下来,我们通过代码demo,阐述一下CompletableFuture使用的几个坑~
1.默认线程池的坑
CompletableFuture默认使用ForkJoinPool.commonPool() 作为线程池。如果任务阻塞或执行时间过长,可能会导致线程池耗尽,影响其他任务的执行。
反例:
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { // 模拟长时间任务 try { Thread.sleep(10000); System.out.println("捡田螺的小男孩666"); } catch (InterruptedException e) { e.printStackTrace(); } }); future.join();
正例:
使用自定义线程池来避免默认线程池的瓶颈。
// 1. 手动创建线程池(核心参数可配置化)int corePoolSize = 10; // 核心线程数int maxPoolSize = 10; // 最大线程数(固定大小)long keepAliveTime = 0L; // 非核心线程空闲存活时间(固定线程池可设为0)BlockingQueue<Runnable> workQueue = new ArrayBlockingQueue<>(100); // 有界队列(容量100)RejectedExecutionHandler rejectionHandler = new ThreadPoolExecutor.AbortPolicy(); // 拒绝策略ExecutorService customExecutor = new ThreadPoolExecutor( corePoolSize, maxPoolSize, keepAliveTime, TimeUnit.MILLISECONDS, workQueue, rejectionHandler);// 2. 提交异步任务CompletableFuture<Void> future = CompletableFuture.runAsync(() -> { try { Thread.sleep(10000); // 模拟耗时任务 System.out.println("捡田螺的小男孩666"); System.out.println("Task completed"); } catch (InterruptedException e) { e.printStackTrace(); }}, customExecutor);// 3. 阻塞等待任务完成future.join();
2. 异常处理的坑
CompletableFuture 异常处理机制,跟我们使用的传统try...catch有点不一样的。
正例:
使用 exceptionally 或 handle 方法来处理异常。
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { throw new RuntimeException("田螺测试异常!"); }); future.exceptionally(ex -> { System.err.println("异常: " + ex.getMessage()); return -1; // 返回默认值 }).join();
运行结果:
异常: java.lang.RuntimeException: 田螺测试异常!
3. 超时处理的坑
CompletableFuture 本身不支持超时处理,如果任务长时间不完成,可能会导致程序一直等待。
反例:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(10000); //模拟任务耗时 } catch (InterruptedException e) { e.printStackTrace(); } return 1;});future.join(); // 程序会一直等待
正例:
如果你是JDK8,使用 get() 方法并捕获 TimeoutException
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } return 1; }); future.join(); // 程序会一直等待 try { Integer result = future.get(3, TimeUnit.SECONDS); // 设置超时时间为1秒 System.out.println("田螺等待3秒之后:"+result); } catch (TimeoutException e) { System.out.println("Task timed out"); future.cancel(true); // 取消任务 } catch (Exception e) { e.printStackTrace(); }
如果你是Java 9 或更高版本,可以直接使用 orTimeout 和 completeOnTimeout 方法:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> { try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } return 1;}).orTimeout(3, TimeUnit.SECONDS); // 3秒超时future.exceptionally(ex -> { System.err.println("Timeout: " + ex.getMessage()); return -1;}).join();
4. 线程上下文传递的坑
CompletableFuture 默认不会传递线程上下文(如 ThreadLocal),这可能导致上下文丢失~~
ThreadLocal<String> threadLocal = new ThreadLocal<>(); threadLocal.set("田螺主线程"); CompletableFuture.runAsync(() -> { System.out.println(threadLocal.get()); // 输出 null }).join();
正例:
使用CompletableFuture 的supplyAsync 或 runAsync时,手动传递上下文。
ThreadLocal<String> threadLocal = new ThreadLocal<>(); threadLocal.set("田螺主线程"); ExecutorService executor = Executors.newFixedThreadPool(1); CompletableFuture.runAsync(() -> { threadLocal.set("田螺子线程"); System.out.println(threadLocal.get()); // 输出田螺子线程 }, executor).join();
5. 回调地狱的坑
CompletableFuture 的回调地狱指的是在异步编程中,过度依赖回调方法(如 thenApply、thenAccept 等)导致代码嵌套过深、难以维护的现象。
当多个异步任务需要顺序执行或依赖前一个任务的结果时,如果直接嵌套回调,代码会变得臃肿且难以阅读。反例如下:
CompletableFuture.supplyAsync(() -> 1) .thenApply(result -> { System.out.println("Step 1: " + result); return result + 1; }) .thenApply(result -> { System.out.println("Step 2: " + result); return result + 1; }) .thenAccept(result -> { System.out.println("Step 3: " + result); });
正例:
通过链式调用和方法拆分,保持代码简洁:
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> 1) .thenApply(this::step1) .thenApply(this::step2);future.thenAccept(this::step3);// 拆分逻辑到单独方法private int step1(int result) { System.out.println("Step 1: " + result); return result + 1;}private int step2(int result) { System.out.println("Step 2: " + result); return result + 1;}private void step3(int result) { System.out.println("Step 3: " + result);}
6. 任务编排,执行顺序混乱的坑
任务编排时,如果任务之间有依赖关系,可能会导致任务无法按预期顺序执行。
反例:
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 1);CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> 2);CompletableFuture<Integer> result = future1.thenCombine(future2, (a, b) -> a + b);result.join(); // 可能不会按预期顺序执行
正例:
使用 thenCompose 或 thenApply 来确保任务顺序。
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> 1);CompletableFuture<Integer> future2 = future1.thenApply(a -> a + 2);future2.join(); // 确保顺序执行
推荐阅读:
严禁加班,强制下班,真的发生了。。。
我工资1w,跳槽到新公司直接开出了1.5w,我象征性地说:我考虑一下。结果当天下午,HR电话给我说可以涨到2w,可我反而不敢去了
在公司干了五年,工资不如刚入职的校招生
为什么阿里绩效用3.25、3.5、3.75表达差、一般、优秀?1、2、3不是更简单