喜提JDK的BUG一枚!多线程的情况下请谨慎使用这个类的stream遍历。

前段时间在 RocketMQ 的 ISSUE 里面冲浪的时候,看到一个 pr,虽说是在 RocketMQ 的地盘上发现的,但是这个玩意吧,其实和 RocketMQ 没有任何关系。

纯纯的就是 JDK 的一个 BUG。

我先问你一个问题:LinkedBlockingQueue 这个玩意是线程安全的吗?

这都是老八股文了,你要是不能脱口而出,应该是要挨板子的。

答案是:是线程安全的,因为有这两把锁的存在。

但是在 RocketMQ 的某个场景下,居然稳定复现了 LinkedBlockingQueue 线程不安全的情况。

先说结论: LinkedBlockingQueue 的 stream 遍历的方式,在多线程下是有一定问题的,可能会出现死循环。

老有意思了,这篇文章带大家盘一盘。

搞个Demo

Demo 其实都不用我搞了,前面提到的 pr 的链接是这个:

https://github.com/apache/rocketmq/pull/3509

在这个链接里面,前面围绕着 RocketMQ 讨论了很多。

但是在中间部分,一个昵称叫做 areyouok 的大佬一针见血,指出了问题的所在。

直接给出了一个非常简单的复现代码。而且完全把 RocketMQ 的东西剥离了出去:

正所谓前人栽树后人乘凉,既然让我看到了 areyouok 这位大佬的代码,那我也就直接拿来当做演示的 Demo 了。

如果你不建议的话,为了表示我的尊敬,我斗胆说一声:感谢雷总的代码。

我先把雷总的代码粘出来,方便看文章的你也实际操作一把:

public class TestQueue {
    public static void main(String[] args) throws Exception {
        LinkedBlockingQueue<Object> queue = new LinkedBlockingQueue<>(1000);
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                while (true) {
                    queue.offer(new Object());
                    queue.remove();
                }
            }).start();
        }
        while (true) {
            System.out.println("begin scan, i still alive");
            queue.stream()
                    .filter(o -> o == null)
                    .findFirst()
                    .isPresent();
            Thread.sleep(100);
            System.out.println("finish scan, i still alive");
        }
    }
}

介绍一下上面的代码的核心逻辑。

首先是搞了 10 个线程,每个线程里面在不停的调用 offer 和 remove 方法。

需要注意的是这个 remove 方法是无参方法,意思是移除头节点。

再强调一次:LinkedBlockingQueue 里面有 ReentrantLock 锁,所以即使多个线程并发操作 offer 或者 remove 方法,也都要分别拿到锁才能操作,所以这一定是线程安全的。

然后主线程里面搞个死循环,对 queue 进行 stream 操作,看看能不能找到队列里面第一个不为空的元素。

这个 stream 操作是一个障眼法,真正的关键点在于 tryAdvance 方法:

先在这个方法这里插个眼,一会再细嗦它。

按理来说,这个方法运行起来之后,应该不停的输出这两句话才对:

begin scan, i still alive
finish scan, i still alive

但是,你把代码粘出去用 JDK 8 跑一把,你会发现控制台只有这个玩意:

或者只交替输出几次就没了。

但是当我们不动代码,只是替换一下 JDK 版本,比如我刚好有个 JDK 15,替换之后再次运行,交替的效果就出来了:

那么基于上面的表现,我是不是可以大胆的猜测,这是 JDK 8 版本的 BUG 呢?

现在我们有了能在 JDK 8 运行环境下稳定复现的 Demo,接下来就是定位 BUG 的原因了。

啥原因呀?

先说一下我拿到这个问题之后,排查的思路。

非常的简单,你想一想,主线程应该一直输出但是却没有输出,那么它到底是在干什么呢?

我初步怀疑是在等待锁。

怎么去验证呢?

朋友们&

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值