Doug Lea在J.U.C包里面写的BUG又被网友发现了(3)

简介: Doug Lea在J.U.C包里面写的BUG又被网友发现了(3)

另外,我觉得这个 BUG 的提交者自己应该解释我们为什么需要修改这部分代码。


其实 Doug 的言外之意就是:你说这部分有问题,你给我举个例子,别只是整理论的,你弄点代码给我看看。


image.png


他说:哦,原来是故意的呀。


这句话,你用不同的语气可以读出不同的含义。


我这里倾向于他觉得既然 Doug 当初写这段代码的时候考虑到了这点,他分析之后觉得自己这样写是没有问题的,就这样写了。


好嘛,前面说 INTERRUPING 不需要特殊处理,现在说 COMPLETING 状态是检测不到的。


那就没得玩了。


image.png


事情现在看起来已经是被定性了,那就是不需要进行修改。


但是就在这时 Paul 同学杀了个回马枪,应该也是前面的讨论激发了他的思路,你不是说检测不出来吗,你不是说 get 方法可以获得最终的正确结果吗?


那你看看我这段代码是什么情况:


image.png


代码是这样的,大家可以直接粘贴出来,在 JDK 8/9 环境下分别运行一下:


public static void main(String[] args) throws Exception {
        AtomicReference<FutureTask<Integer>> a = new AtomicReference<>();
        Runnable task = () -> {
            while (true) {
                FutureTask<Integer> f = new FutureTask<>(() -> 1);
                a.set(f);
                f.run();
            }
        };
        Supplier<Runnable> observe = () -> () -> {
            while (a.get() == null);
            int c = 0;
            int ic = 0;
            while (true) {
                c++;
                FutureTask<Integer> f = a.get();
                while (!f.isDone()) {}
                try {
                    /*
                    Set the interrupt flag of this thread.
                    The future reports it is done but in some cases a call to
                    "get" will result in an underlying call to "awaitDone" if
                    the state is observed to be completing.
                    "awaitDone" checks if the thread is interrupted and if so
                    throws an InterruptedException.
                     */
                    Thread.currentThread().interrupt();
                    f.get();
                }
                catch (ExecutionException e) {
                    throw new RuntimeException(e);
                }
                catch (InterruptedException e) {
                    ic ++;
                    System.out.println("InterruptedException observed when isDone() == true " + c + " " + ic + " " + Thread.currentThread());
                }
            }
        };
        CompletableFuture.runAsync(task);
        Stream.generate(observe::get)
                .limit(Runtime.getRuntime().availableProcessors() - 1)
                .forEach(CompletableFuture::runAsync);
        Thread.sleep(1000);
        System.exit(0);
    }


先看一下这段代码的核心逻辑:


微信图片_20220426225907.png


首先标号为 ① 的地方是两个计数器,c 代表的是第一个 while 循环的次数,ic 代表的是抛出 InterruptedException(IE) 的次数。


标号为 ② 的地方是判断当前任务是否是完成状态,如果是,则继续往下。


标号为 ③ 的地方是先中断当前线程,然后调用 get 方法获取任务结果。


标号为 ④ 的地方是如果 get 方法抛出了 IE 异常,则在这里进行记录,打印日志。


需要注意的是,如果打印日志了,说明了一个问题:


前面明明 isDone 方法返回 true 了,说明方法执行完成了。但是我调用 get 方法的时候却抛出了 IE 异常?


这你怕是有点说不通吧!


JDK 8 的运行结果我给大家截个图。


image.png


这个异常是在哪里被抛出来的呢?


awaitDone 方法的入口处,就先检查了当前线程是否被中断,如果被中断了,那么抛出 IE 异常:


image.png


任务状态是小于等于 COMPLETING 的时候。


在示例代码中,前面的 while 循环中的 isDone 方法已经返回了 true,说明当前状态肯定不是 NEW。


那么只剩下个什么东西了?


就只有一个 COMPLETING 状态了。


小样,这不就是监测到了吗?


image.png


在这段示例代码出来后的第 8 个小时,David 靓仔又来说话了:


image.png


他要表达的意思,我理解的是这样的:


在 j.u.c 包里面,优先检查线程中断状态是很常见的操作,因为相对来说,会导致线程中断的地方非常的少。


但是不能因为少,我们就不检查了。


我们还是得对其进行了一个优先检查,告知程序当前线程是否发生了中断,即是否有继续往下执行的意义。


但是,在这个场景中,当前线程中断了,但并不能表示 Future 里面的 task 任务的完成情况。这是两个不相关的事情。


即使当前线程中断了,但是 task 任务仍然可以继续完成。但是执行 get 方法的线程被中断了,所以可能会抛出 InterruptedException。


因此,他给出的解决建议是:


可以选择优先返回结果,在 awaitDone 方法的循环中把检查中断的代码挪到后面去。


五天之后,之前 BUG 的提交者 Martin 同学又来了:


image.png

目录
相关文章
|
9天前
|
安全 Java 编译器
一个 Bug JDK 居然改了十年?
你敢相信么一个简单的Bug,JDK 居然花了十年时间才修改完成。赶快来看看到底是个什么样的 Bug?
19 1
一个 Bug JDK 居然改了十年?
|
6月前
|
NoSQL 程序员 C语言
探秘Segmentation Fault错误:程序猿的噩梦
探秘Segmentation Fault错误:程序猿的噩梦
|
7月前
|
NoSQL Java Redis
阿里P8熬了一个月肝出这份32W字Java面试手册,在Github标星31K+
互联网行业竞争越来越严峻,面试也是越来越难,一直以来我都想整理一套完美的面试宝典,奈何难抽出时间,这套1000+道的Java面试手册我整理了整整1个月,上传到Git上目前star数达到了30K+
|
监控 Java 测试技术
No.7 一篇文章讲清楚golang内存泄漏
No.7 一篇文章讲清楚golang内存泄漏
521 1
No.7 一篇文章讲清楚golang内存泄漏
|
Cloud Native Go 开发者
那些年,我们追过的Go BUG
那些年,我们追过的Go BUG
116 0
|
存储 Rust 供应链
编写完10万行代码,我发了篇长文吐槽Rust
编写完10万行代码,我发了篇长文吐槽Rust
181 0
|
数据可视化 开发工具 git
|
开发工具 git
如何使用TortoiseGit指北,一篇讲清(下)
如何使用TortoiseGit指北,一篇讲清(下)
如何使用TortoiseGit指北,一篇讲清(下)
|
Java 编译器 Go
字节跳动青训营Day04 - Go编译器优化
静态分析:不执行代码,推导程序的行为,分析程序的性质。 控制流:程序的执行流程 数据流:数据在控制流上的传递
233 0
字节跳动青训营Day04 - Go编译器优化