我惊了!CompletableFuture居然有性能问题! (下)

简介: 我惊了!CompletableFuture居然有性能问题! (下)

到底啥原因?


前面噼里啪啦的说了这么大一段,核心思想其实就是 Runtime.availableProcessors 方法的调用成本高,所以在 CompletableFuture.waitingGet 方法中不应该频繁调用这个方法。

但是 availableProcessors 为什么调用成本就高了,依据是啥,得拿出来看看啊!

这一小节,就给大家看看依据是什么。

依据就在这个 BUG 描述中:

https://bugs.openjdk.java.net/browse/JDK-8227006


image.png

标题上说:在 linux 环境下,Runtime.availableProcessors 执行时间增加了 100 倍。

增加了 100 倍,肯定是有两个不同的版本的对比,那么是哪两个版本呢?

image.png

在 1.8b191 之前的 JDK 版本上,下面的示例程序可以实现每秒 400 多万次对 Runtime.availableProcessors 的调用。

但在 JDK build 1.8b191 和所有后来的主要和次要版本(包括11)上,它能实现的最大调用量是每秒4万次左右,性能下降了100倍。

这就导致了 CompletableFuture.waitingGet 的性能问题,它在一个循环中调用了 Runtime.availableProcessors。因为我们的应用程序在异步代码中表现出明显的性能问题,waitingGet 就是我们最初发现问题的地方。

测试代码是这样的:

public static void main(String[] args) throws Exception {
        AtomicBoolean stop = new AtomicBoolean();
        AtomicInteger count = new AtomicInteger();
        new Thread(() -> {
            while (!stop.get()) {
                Runtime.getRuntime().availableProcessors();
                count.incrementAndGet();
            }
        }).start();
        try {
            int lastCount = 0;
            while (true) {
                Thread.sleep(1000);
                int thisCount = count.get();
                System.out.printf("%s calls/sec%n", thisCount - lastCount);
                lastCount = thisCount;
            }
        }
        finally {
            stop.set(true);
        }
    }

按照 BUG 提交者的描述,如果你在 64 位的 Linux 上,分别用 JDK 1.8b182 和 1.8b191 版本去跑,你会发现有近 100 倍的差异。

至于为什么有 100 倍的性能差异,一位叫做 Fairoz Matte 的老哥说他调试了一下,定位到问题出现在调用 “OSContainer::is_containerized()” 方法的时候:


image.png

而且他也定位到了问题出现的最开始的版本号是 8u191 b02,在这个版本之后的代码都会有这样的问题。

带来问题的那次版本升级干的事是改进 docker 容器检测和资源配置的使用。

所以,如果你的 JDK 8 是 8u191 b02 之前的版本,且系统调用并发非常高,那么恭喜你,有机会踩到这个坑。

然后,下面几位大佬基于这个问题给出了很多解决方案,并针对各种解决方案进行讨论。

有的解决方案,听起来就感觉很麻烦,需要编写很多的代码。

最终,大道至简,还是选择了实现起来比较简单的 cache 方案,虽然这个方案也有一点瑕疵,但是出现的概率非常低且是可以接受的。

image.png


再看get方法


现在我们知道了这个没有卵用的知识点之后,我们再看看为什么调用带超时时间的 get() 方法,没有这个问题。

java.util.concurrent.CompletableFuture#get(long, java.util.concurrent.TimeUnit)

首先可以看到内部调用的方法都不一样了:

image.png

有超时时间的 get() 方法,内部调用的是 timedGet 方法,入参就是超时时间。

点进 timedGet 方法就知道为什么调用带超时时间的 get() 方法没有问题了:


image.png

在代码的注释里面已经把答案给你写好了:我们故意不在这里旋转(像waitingGet那样),因为上面对 nanoTime() 的调用很像一个旋转。

可以看到在该方法内部,根本就没有对 Runtime.availableProcessors 的调用,所以也就不存在对应的问题。

现在,我们回到最开始的地方:

image.png

那么你说,下面的 asyncResult.get(Integer.MAX_VALUE, TimeUnit.MILLISECONDS) 如果我们改成 asyncResult.get() 效果还是一样的吗?

肯定是不一样的。

再说一次:Dubbo 作为开源的中间件,有可能会运行在各种不同的 JDK 版本中,且该方法是它主链路上的核心代码,对于特定的 JDK 版本来说,这个优化确实是对于性能的提升有很大的帮助。

所以写中间件还是有点意思哈。

最后,再送你一个为 Dubbo 提交源码的机会。

在其下面的这个类中:

org.apache.dubbo.rpc.AsyncRpcResult

还是存在这两个方法:

image.png

完全可以把它们全部改掉调用 get(long timeout, TimeUnit unit) 方法,然后把 get() 方法直接删除了。

我觉得肯定是能被 merge 的。

如果你想为开源项目做贡献,熟悉一下流程,那么这是一个不错的小机会。

image.png

目录
相关文章
|
5月前
|
Java 开发者
奇迹时刻!探索 Java 多线程的奇幻之旅:Thread 类和 Runnable 接口的惊人对决
【8月更文挑战第13天】Java的多线程特性能显著提升程序性能与响应性。本文通过示例代码详细解析了两种核心实现方式:Thread类与Runnable接口。Thread类适用于简单场景,直接定义线程行为;Runnable接口则更适合复杂的项目结构,尤其在需要继承其他类时,能保持代码的清晰与模块化。理解两者差异有助于开发者在实际应用中做出合理选择,构建高效稳定的多线程程序。
68 7
|
4月前
|
Android开发 开发者 Kotlin
告别AsyncTask:一招教你用Kotlin协程重构Android应用,流畅度飙升的秘密武器
【9月更文挑战第13天】随着Android应用复杂度的增加,有效管理异步任务成为关键。Kotlin协程提供了一种优雅的并发操作处理方式,使异步编程更简单直观。本文通过具体示例介绍如何使用Kotlin协程优化Android应用性能,包括网络数据加载和UI更新。首先需在`build.gradle`中添加coroutines依赖。接着,通过定义挂起函数执行网络请求,并在`ViewModel`中使用`viewModelScope`启动协程,结合`Dispatchers.Main`更新UI,避免内存泄漏。使用协程不仅简化代码,还提升了程序健壮性。
132 1
|
5月前
|
Java 开发者 UED
【揭秘Java编程新境界】事件驱动:如何在Java中捕捉每一个关键瞬间?
【8月更文挑战第30天】事件驱动编程是一种编程范式,使程序能在事件发生时响应,而非按严格顺序执行。本文介绍Java中的事件驱动编程,包括基本概念、优势及其实现方法。通过事件监听器和事件对象,Java能够高效处理GUI、网络编程和游戏开发中的各种事件。文中还提供了创建事件监听器、自定义事件及处理多个事件源的示例代码,帮助读者更好地理解和应用这一强大的编程范式。
120 1
|
5月前
|
Java
【Java集合类面试三十】、BlockingQueue中有哪些方法,为什么这样设计?
BlockingQueue设计了四组不同行为方式的方法用于插入、移除和检查元素,以适应不同的业务场景,包括抛异常、返回特定值、阻塞等待和超时等待,以实现高效的线程间通信。
|
5月前
|
安全 Java 程序员
大家都说Java有三种创建线程的方式!并发编程中的惊天骗局!
今天来聊一个比较有意思的话题,这是一道Java八股文中的八股文,简称八股文Plus!
|
8月前
|
Java 测试技术 索引
滚雪球学Java(15):节约时间,提升效率:掌握JavaSE-while循环语句的技巧与窍门
【4月更文挑战第4天】🏆本文收录于「滚雪球学Java」专栏,专业攻坚指数级提升,希望能够助你一臂之力,帮你早日登顶实现财富自由🚀;同时,欢迎大家关注&&收藏&&订阅!持续更新中,up!up!up!!
67 3
滚雪球学Java(15):节约时间,提升效率:掌握JavaSE-while循环语句的技巧与窍门
|
8月前
|
监控 安全 Java
CompletableFuture探秘:解锁Java并发编程的新境界
CompletableFuture探秘:解锁Java并发编程的新境界
252 0
|
消息中间件 存储 Java
一网打尽异步神器CompletableFuture
最近一直畅游在RocketMQ的源码中,发现在RocketMQ中很多地方都使用到了CompletableFuture,所以今天就跟大家来聊一聊JDK1.8提供的异步神器CompletableFuture,并且最后会结合RocketMQ源码分析一下CompletableFuture的使用。
|
Java
异步利刃CompletableFuture
异步利刃CompletableFuture
107 0
|
缓存 负载均衡 Dubbo
我惊了!CompletableFuture居然有性能问题! (上)
我惊了!CompletableFuture居然有性能问题! (上)
507 0
我惊了!CompletableFuture居然有性能问题! (上)

热门文章

最新文章