CompletableFuture使用的6个坑

图片

来源 | 捡田螺的小男孩(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不是更简单

  • 校招了个年轻人,工作太被动,不懂主动找活,我批评了他一次后,第二天竟然找我提离职了...
  • (0)
    wd123_cnwd123_cn
    上一篇 2025年3月17日 上午10:16
    下一篇 2025年3月17日

    相关文章

    • 南极冰盖融化或激活火山,加速海平面上升:一项被忽视的气候风险

      当火山爆发登上新闻头条时,我们看到的往往是炙热的熔岩和高耸的火山灰云。然而,在南极洲冰封的景观之下,火山正以一种令人惊讶的方式悄然影响着地球的气候。最近的科学研究表明,西南极洲冰盖的融化可能会引发火山活动,从而形成一个加速冰损失和海平面上升的循环。 冰盖、火山与气候的相互作用 研究地球地质历史的科学家发现,被冰盖覆盖的火山在冰融化时会产生强烈的反应。随着厚厚…

      2025年4月7日
    • “小米奇,这是最后一次找你吗?”杜小华称若再无音讯将放缓寻子脚步:先努力挣钱

      来源 | 潇湘晨报 记者 | 吴陈幸子 “小米奇,这是最后一次找你吗?”3月9日,寻子家属杜小华发布视频称,自己再次前往儿子小米奇的丢失地点包头寻找相关线索,但“往后可能会放缓寻找小米奇的脚步”。 杜小华称,小米奇的奶奶年纪太大了,不敢太长时间在外面,小米奇的弟弟妹妹也马上进入青春叛逆期,小米奇的母亲一个人既要守店做生意养活一家人,又要照顾奶奶和弟弟妹妹,已…

      2025年3月12日
    • “从陵水漂到三亚”海钓男孩父亲发声:村里抽干水塘找孩子?此事不属实

      来源 | 潇湘晨报、极目新闻 记者 | 张沁 3月22日,海南一位网友称,其出海钓鱼时在海上“捡到”一名划桨板漂流的10岁男孩,并将其送回岸上。3月23日,极目新闻记者从男孩父亲处了解到,其儿子已平安回家,网传的“村里将水塘抽干寻孩子”说法不实。 此前报道 从陵水到三亚!10岁男孩钓鱼在海上迷路后漂流一天一夜获救, 多方讲述 3月22日,海南一名网…

      2025年3月24日
    • 《纽约时报》新文字游戏《Strands》引发热潮

      继Wordle、Connections和Mini Crossword之后,《纽约时报》推出了一款新的文字游戏《Strands》,迅速成为玩家们每日必玩的新宠。这款游戏以每日主题和“spangram”为特色,玩家需要在24小时内完成挑战,迎接新的谜题。 如何玩《Strands》 根据《纽约时报》的介绍,玩家需要找到与主题相关的单词来填满游戏板。找到的主题单词会…

      2025年3月8日
    • 赛琳娜·戈麦斯与未婚夫本尼·布兰科:婚礼计划与全新合作专辑同步进行

      “谁说”赛琳娜·戈麦斯和本尼·布兰科正急于步入婚姻殿堂?这对新晋订婚情侣近日接受了《滚石》杂志的采访,畅谈了他们的婚礼计划、即将推出的合作专辑《我说我爱你优先》以及其他话题。 婚礼计划:一切顺其自然 据37岁的布兰科透露,32岁的戈麦斯对于她梦想中的婚礼,在时机成熟时,已经有了不少想法。“我觉得她每天都在脑海里策划一场新的婚礼,”布兰科笑着说,“但我们都属于…

      2025年3月18日
    • 《叛徒》第三季大结局揭秘:詹恩和加比的真人秀经验助力游戏策略

      在《叛徒》第三季的最后一集中,詹恩和加比分享了她们在真人秀《单身女郎》中的经验如何帮助她们在Peacock的游戏节目中制定策略。詹恩告诉《人物》杂志,她认为同时与25位男士约会的经历让她更好地读懂人心。加比在3月6日的季终重聚节目中也表达了类似的感受,她告诉主持人安迪·科恩,她在《单身女郎》中的经历帮助她在《叛徒》的城堡中更好地应对挑战。 加比在3月5日宣布…

      2025年3月8日