把之前CompletableFuture留下的坑给填上。 (上)

简介: 把之前CompletableFuture留下的坑给填上。 (上)

你好呀,我是歪歪。

填个坑吧,把之前一直欠着的 CompletableFuture 给写了,因为后台已经收到过好几次催更的留言了。

这玩意我在之前写的这篇文章中提到过:《面试官问我知不知道异步编程的Future》

因为是重点写 Future 的,所以 CompletableFuture 只是在最后一小节的时候简单的写了一下:

image.png

我就直接把当时的例子拿过来改一下吧,先把代码放在这里了:

public class MainTest {
    public static void main(String[] args) throws Exception {
        CompletableFuture.supplyAsync(() -> {
            System.out.println(Thread.currentThread().getName() + "-女神:我开始化妆了,好了我叫你。");
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return "化妆完毕了。";
        }).whenComplete((returnStr, exception) -> {
            if (exception == null) {
                System.out.println(Thread.currentThread().getName() + returnStr);
            } else {
                System.out.println(Thread.currentThread().getName() + "女神放你鸽子了。");
                exception.printStackTrace();
            }
        });
        System.out.println(Thread.currentThread().getName() + "-等女神化妆的时候可以干点自己的事情。");
        Thread.currentThread().join();
    }
}

核心需求就是女神化妆的时候,我可以先去干点自己的事情。

上面的程序运行结果是这样的:



image.png


符合我们的预期,没有任何毛病。

但是当你自己去编写程序的时候,有可能会遇到这样的情况

image.png

image.png

是的,这就是我要说的第一个关于 CompletableFuture 的知识点:守护线程。


守护线程


你仔细观察前面提到的两个截图,对比一下他们的第 28 行,第二个截图少了一行代码:

Thread.currentThread().join();

这行代码是在干啥事呢?

目的就是阻塞主线程,哪怕你让主线程睡眠也行,反正目的就是把主线程阻塞住。

如果没有这行代码,出现的情况就是主线程直接运行完了,程序也就结束了。

你想想,会是什么原因?

这个时候你脑海里面应该啪的一下,很快就想到“守护线程”这个概念。

主线程是用户线程,这个没啥说的。

所有的用户线程执行完成后, JVM 也就退出了。

因此,出现上面问题的原因我有合理的理由猜测:CompletableFuture 里面执行的任务属于守护线程。

有了理论知识的支撑,并推出这个假设之后,就有了证实的方向,问题就很简单了。

啪的一下在这里打上一个断点,然后 Debug 起来,表达式一写就看出来了,确实是守护线程:

image.png

我一般是想要看到具体的代码的,就是得看到把这个线程设置为守护线程的那一行代码,我才会死心。

所以我就去找了一下,还是稍微花了点时间,过程就不描述了,直接说结论吧。

首先 CompletableFuture 默认的线程池是 ForkJoinPool,这个是很容易就能在源码里面找到的:

image.png

在 ForkJoinPool 里面,把线程都设置为守护线程的地方就在这里:

java.util.concurrent.ForkJoinPool#registerWorker

image.png

image.png

image.png

前面大概就是说 shutdown 和 shutdownNow 对于这个线程池来说没用。

如果,线程池里面的任务需要在程序终止前完成,那么应该在退出前调用 commonPool().awaitQuiescence。

image.png

所以,我的程序应该改成这样:

image.png

可以,不错,很优雅。

image.png

如果,你的异步任务非常重要,必须要执行完成,那么 ForkJoinPool 也给你封装好了一个方法:

java.util.concurrent.ForkJoinPool#quiesceCommonPool

image.png

image.png

加入指定线程池的逻辑,注释掉主线程 join 的代码,跑起来之后。诶,JVM 一直都在。

你说神奇不神奇?



和 Future 对比


CompletableFuture 其实就是 Future 的升级版。

Future 有的,它都有。

Future 的短板,它补上了。

毕竟一个是 JDK 1.5 时代的产物,另一个是 1.8 时代的作品:

image.png

image.png

所以,后来居上。

给大家对比一下 Future 和 CompletableFuture。

首先对于我个人而言,第一个最直观的感受是获取结果的姿势舒服多了。

我不得不又把这张图拿出来说说了,主要关注下面的两种 future 和 callback:

微信图片_20220428204250.jpg


当我们用 Future 去实现异步,要获取异步结果的时候,是怎么样操作的?

是不是得调用 future.get() 方法去取值。

如果这个时候值已经准备就绪,在 future 里面封装好了,那么万事大吉,直接拿出来就可以用。

但是如果值还没有准备好呢?

是不是就阻塞等待了?

所以我常常说 Future 是一种阉割版的异步模式。

比如还是最开始的例子,如果我用 Future 来做,那么是这样的:

image.png

你仔细看我框起来的地方,是 main 线程开始获取结果,获取结果的这个动作把 main 线程给阻塞住了。

你就去洗不了头了,老弟。

好,你说你把获取结果的操作放到最后,没问题。

但是,无论你放在哪里,你都有一个 get 的动作,且你执行这个动作的时候,你也不知道值到底准备好了没,所以有可能出现阻塞等待的情况。

好,那么问题来了:如果消除这个阻塞等待呢?

很简单,换个思路,我们从主动问询,变成等待通知。

女神化妆好了之后,主动通知一下我不就好了吗?

用程序员的话说就是:运行结果出来了,你执行一下我留给你的回调函数不就好了吗?

CompletableFuture 就可以干这个事儿。

用 CompletableFuture 写一遍上面的程序就是这样的:

image.png

pool-1-thread-1,女神化妆的这个线程,她好了之后会主动叫你,你明白吗?

这就是我第一次接触到 CompletableFuture 后,学到的第一个让我感到舒服的地方。

这种写法你注意,whenComplete(returnStr, exception) 返回信息和异常信息在这里都有了。

除此之外,这个方法还是带返回值的,你也完全可以像是用 Future 那样通过 get 获取其返回值:

image.png


目录
相关文章
|
4天前
|
Java 程序员
Java社招面试中的高频考点:Callable、Future与FutureTask详解
大家好,我是小米。本文主要讲解Java多线程编程中的三个重要概念:Callable、Future和FutureTask。它们在实际开发中帮助我们更灵活、高效地处理多线程任务,尤其适合社招面试场景。通过 Callable 可以定义有返回值且可能抛出异常的任务;Future 用于获取任务结果并提供取消和检查状态的功能;FutureTask 则结合了两者的优势,既可执行任务又可获取结果。掌握这些知识不仅能提升你的编程能力,还能让你在面试中脱颖而出。文中结合实例详细介绍了这三个概念的使用方法及其区别与联系。希望对大家有所帮助!
92 60
|
8月前
|
存储 算法 Java
如果面试也能这样说HashMap,那么就不会有那么多遗憾!(中)
如果面试也能这样说HashMap,那么就不会有那么多遗憾!
51 0
|
8月前
|
安全 Java 调度
HashMap很美好,但线程不安全怎么办?ConcurrentHashMap告诉你答案!
HashMap很美好,但线程不安全怎么办?ConcurrentHashMap告诉你答案!
115 1
|
8月前
|
Java
面试官:小伙子来说一说Java中final关键字,以及它和finally、finalize()有什么区别?
面试官:“小伙子,用过final关键字吗?” 我:“必须用过呀” 面试官:“好,那来说一说你对这个关键字的理解吧,再说一说它与finally、finalize()的区别” 我:“好嘞!
56 1
|
8月前
|
存储 机器学习/深度学习 算法
如果面试也能这样说HashMap,那么就不会有那么多遗憾!(上)
如果面试也能这样说HashMap,那么就不会有那么多遗憾!
76 1
|
8月前
|
存储 Java 索引
如果面试也能这样说HashMap,那么就不会有那么多遗憾!(下)
如果面试也能这样说HashMap,那么就不会有那么多遗憾!
173 0
|
Java
Java并发编程:深入理解CompletableFuture
一、引言 在Java的世界中,多线程编程一直被誉为其独特优势之一,而在Java 8中引入的CompletableFuture则为这一领域提供了更加强大和灵活的工具。本文将对CompletableFuture进行深度剖析,带你领略其在多线程开发中的实力。
130 0
|
Java 开发者 Sentinel
CompletableFuture学习整理
整理的原因是在Sentinel源码中,我们可以看到很多关于CompletableFuture的thenCompose的源码。同时在业务系统里面也看到别人写过类似的代码。因此整理了一下关于CompletableFuture使用的相关类型和特性,在处理复杂耗时业务时可以选择组合使用。
233 0
CompletableFuture学习整理
|
消息中间件 Dubbo Kafka
CompletableFuture学习
前面我们已经知道CompletionService是可以解决Future带来的阻塞问题的,同时我们除了前面我们看到的take方法之外,还可以使用poll方法,这样可以使你的程序免受阻塞之苦。因为poll方法也是无阻塞性的。同时在kafka的源码中,我们如果使用消费者的话,可以看到会使用一个基于future的poll方法。同时我们可以在dubbo的新版本2.7中,可以看到其异步编程采用的就是我们要介绍的CompletableFuture。因此,我们有必要了解CompletableFuture,同时其也是真正意义上的异步编程的实现。
162 0
CompletableFuture学习