另外,我觉得这个 BUG 的提交者自己应该解释我们为什么需要修改这部分代码。
其实 Doug 的言外之意就是:你说这部分有问题,你给我举个例子,别只是整理论的,你弄点代码给我看看。
他说:哦,原来是故意的呀。
这句话,你用不同的语气可以读出不同的含义。
我这里倾向于他觉得既然 Doug 当初写这段代码的时候考虑到了这点,他分析之后觉得自己这样写是没有问题的,就这样写了。
好嘛,前面说 INTERRUPING 不需要特殊处理,现在说 COMPLETING 状态是检测不到的。
那就没得玩了。
事情现在看起来已经是被定性了,那就是不需要进行修改。
但是就在这时 Paul 同学杀了个回马枪,应该也是前面的讨论激发了他的思路,你不是说检测不出来吗,你不是说 get 方法可以获得最终的正确结果吗?
那你看看我这段代码是什么情况:
代码是这样的,大家可以直接粘贴出来,在 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); }
先看一下这段代码的核心逻辑:
首先标号为 ① 的地方是两个计数器,c 代表的是第一个 while 循环的次数,ic 代表的是抛出 InterruptedException(IE) 的次数。
标号为 ② 的地方是判断当前任务是否是完成状态,如果是,则继续往下。
标号为 ③ 的地方是先中断当前线程,然后调用 get 方法获取任务结果。
标号为 ④ 的地方是如果 get 方法抛出了 IE 异常,则在这里进行记录,打印日志。
需要注意的是,如果打印日志了,说明了一个问题:
前面明明 isDone 方法返回 true 了,说明方法执行完成了。但是我调用 get 方法的时候却抛出了 IE 异常?
这你怕是有点说不通吧!
JDK 8 的运行结果我给大家截个图。
这个异常是在哪里被抛出来的呢?
awaitDone 方法的入口处,就先检查了当前线程是否被中断,如果被中断了,那么抛出 IE 异常:
任务状态是小于等于 COMPLETING 的时候。
在示例代码中,前面的 while 循环中的 isDone 方法已经返回了 true,说明当前状态肯定不是 NEW。
那么只剩下个什么东西了?
就只有一个 COMPLETING 状态了。
小样,这不就是监测到了吗?
在这段示例代码出来后的第 8 个小时,David 靓仔又来说话了:
他要表达的意思,我理解的是这样的:
在 j.u.c 包里面,优先检查线程中断状态是很常见的操作,因为相对来说,会导致线程中断的地方非常的少。
但是不能因为少,我们就不检查了。
我们还是得对其进行了一个优先检查,告知程序当前线程是否发生了中断,即是否有继续往下执行的意义。
但是,在这个场景中,当前线程中断了,但并不能表示 Future 里面的 task 任务的完成情况。这是两个不相关的事情。
即使当前线程中断了,但是 task 任务仍然可以继续完成。但是执行 get 方法的线程被中断了,所以可能会抛出 InterruptedException。
因此,他给出的解决建议是:
可以选择优先返回结果,在 awaitDone 方法的循环中把检查中断的代码挪到后面去。
五天之后,之前 BUG 的提交者 Martin 同学又来了: