在java specification 17.3. Sleep and Yield中,它说
重要的是要注意Thread.sleep和Thread.yield都没有任何同步语义。
这句话就是重点。如果我在下面的测试代码中将cv2.cvtColor()
替换为Thread.sleep(100)
,则编译器每次都会读取System.out.println("")
因为iv.stop
获取锁定,请检查this question。 Java规范说System.out.println("")
没有任何同步语义,所以我想知道是什么让编译器将Thread.sleep
视为与Thread.sleep(100)
相同。
我的测试代码:
System.out.println("")
正如上面的评论所说,public class InfiniteLoop {
boolean stop = false;
public static void main(String[] args) throws InterruptedException {
final InfiniteLoop iv = new InfiniteLoop();
Thread t1 = new Thread(() -> {
while (!iv.stop) {
//uncomment this block of code, loop broken
// try {
// Thread.sleep(100);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
}
System.out.println("done");
});
Thread t2 = new Thread(() -> {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
iv.stop = true;
});
t1.start();
t2.start();
}
}
打破了循环,这与Java规范的描述不同:为什么?
答案 0 :(得分:3)
让我们看一下实际所说的文档:
编译器 free 只读一次this.done字段,并在每次执行循环时重用缓存值。这意味着循环永远不会终止,即使另一个线程改变了this.done的值。
看到突出显示的字词“免费”? “free”表示编译器可以读取this.done
一次或不读取。这是编译器的选择。这就是“免费”的含义。你的循环中断是因为编译器看到你的代码并且想到“我每次都会阅读iv.stop
,即使我只能阅读它一次。”
换句话说,保证总是打破循环。您的代码与文档说的完全一样。
答案 1 :(得分:1)
所以我想知道是什么让编译器将Thread.sleep(100)视为与System.out.println相同("")。
在语言定义中肯定没有任何内容表明它们完全相同。在Thread.sleep(...)
时,System.out.println(...)
不会跨越任何内存障碍。您可能会看到的是您的架构上运行线程应用程序的工件。也许线程因CPU争用而被换出,这会强制刷新缓存。如果您在其他操作系统或具有更多内核的硬件上运行此操作,则很可能看不到sleep(...)
做任何事情。
这里的差异也可能是编译器优化。其中没有任何内容的while循环可能甚至不检查stop
字段的值,因为编译器知道在循环内没有任何内容正在更新它并且它不是volatile
。只要添加了一些执行线程状态操作的内容,它就会更改生成的代码,以便field
实际上得到关注。
最终,问题在于在线程之间发布boolean stop
字段。该字段应标记为volatile
以确保其已正确共享。正如您所提到的,当您致电System.out.println(...)
时,这会进出synchronized
块,该块跨越内存屏障,有效更新stop
字段。
答案 2 :(得分:-1)
虽然这个问题已经得到解答,但我觉得其他答案并没有解决问题中的混淆。
如果我们将代码简化为
while (!iv.stop) {
// do something ....
}
然后编译器是免费的(正如其他人所说)只能读取iv.stop一次。重点是:
要强制编译器强制重新读取iv.stop,应将其声明为volatile。
如果没有volatile,编译器可能会或者可能不会改变它是否因为更改循环内容而决定重新读取iv.stop(“做某事......”)但是无法可靠地预测。
你不能在这个上下文中推断出睡眠不使用锁定语义的事实
(第三点提到我认为是问题中的混淆)
关于println()vs sleep()的问题:sleep不使用锁定语义的事实是无关紧要的; println也不使用锁定语义。
println 实现可以使用锁定来确保它自己的线程安全,但是这个事实在调用代码(即你的代码)的范围内是不可见的。 (作为旁注,sleep 实现最终将在其实现中使用某种锁定(在本机代码中)。
从API的角度来看,sleep和println都是静态方法,它们只占用一个参数,因此编译器可能会受到相同的影响,关于它如何在周围的代码中执行优化,但就像我说的那样你不能依赖它。