Java Thread.sleep使编译器每次都读取值

Java Thread.sleep使编译器每次都读取值,java,multithreading,Java,Multithreading,它说,在中国 必须注意的是,Thread.sleep和Thread.yide都没有任何同步语义 这句话是重点。如果我在下面的测试代码中将Thread.sleep(100)替换为System.out.println(“”),编译器每次都会读取iv.stop,因为System.out.println(“”获得锁,请检查。Java规范说Thread.sleep没有任何同步语义,所以我想知道是什么让编译器将Thread.sleep(100)与System.out.println(“”)等同对待 我的测试

它说,在中国

必须注意的是,Thread.sleep和Thread.yide都没有任何同步语义

这句话是重点。如果我在下面的测试代码中将
Thread.sleep(100)
替换为
System.out.println(“”
),编译器每次都会读取
iv.stop
,因为
System.out.println(“”
获得锁,请检查。Java规范说
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();
    }
}
正如上面的评论所说,
Thread.sleep()
打破了循环,这与Java规范的描述不同:为什么

让我们看看这些文件实际上是怎么说的:

编译器可以自由地读取字段this.done一次,并在每次执行循环时重用缓存的值。这意味着循环永远不会终止,即使另一个线程更改了This.done的值

看到突出显示的单词“免费”了吗?“free”表示编译器可以读取
this.done
一次,也可以不读取。这是编译器的选择。这就是“免费”的意思。您的循环中断,因为编译器看到您的代码并认为“我每次都要读
iv.stop
,即使我只能读一次。”

换句话说,不能保证总是打破循环。您的代码的行为与文档所说的完全相同

所以我想知道是什么让编译器将Thread.sleep(100)和System.out.println(“”)处理得一样

当然,在语言的定义中没有任何东西表明它们是完全相同的
Thread.sleep(…)
不会跨越任何内存障碍,而
System.out.println(…)
会跨越任何内存障碍。您可能看到的是线程化应用程序如何在体系结构上运行的工件。可能是CPU争用导致线程被换出,从而强制刷新缓存内存。如果您在不同的操作系统或具有更多内核的硬件上运行此操作,您很可能不会看到
sleep(…)
执行任何操作

这里的区别也可能是编译器优化。while循环中没有任何内容,它甚至可能没有检查
stop
字段的值,因为编译器知道循环中没有任何内容在更新它,而且它不是
易失性的
。一旦添加了执行线程状态操作的内容,它就会更改生成的代码,以便实际关注
字段


最终,问题在于线程之间的
布尔停止
字段的发布。该字段应标记为
volatile
,以确保正确共享。正如您所提到的,当您调用
System.out.println(…)
时,这会进入和退出一个
同步的
块,该块会跨越内存障碍,从而有效地更新
停止
字段。

虽然问题已经得到回答,但我觉得其他答案并不能解决问题中的困惑

如果我们将代码简化为

while (!iv.stop) {
    // do something ....
}
然后编译器就可以自由地(正如其他人所说)读取iv.stop仅一次。重点是:

  • 要强制编译器强制重新读取iv.stop,应将其声明为volatile

  • 如果没有volatile,编译器可能会,也可能不会因为更改循环内容(“做点什么…”)而更改是否决定重新读取iv.stop,但这无法可靠地预测

  • 在这种情况下,您无法推断睡眠不使用锁定语义这一事实有什么特殊之处

(第三点提到我认为问题中的困惑)

关于println()与sleep()的问题:sleep不使用锁定语义这一事实是不相关的;println也不使用锁定语义

println实现可以使用锁定来确保自己的线程安全,但这一事实在调用代码(即您的代码)的范围内是不可见的。(作为旁注,sleep实现最终将在其实现(在本机代码中)的深层使用某种类型的锁定)


从API的角度来看,sleep和println都是采用一个参数的静态方法,因此编译器可能会以同样的方式受到它们的影响,关于如何在周围代码中执行优化,但正如我所说的,您不能依赖于此。

它与任何方法都没有区别。编译器仍然可以自由地不执行通过这种方式,@EJP您能给出一个详细的解释吗?编译器可以自由读取这个字段。只读取一次-这并不意味着编译器“必须”只读取一次;)它看起来已经非常清楚了。“编译器可以自由地不这样执行”的哪一部分你不明白吗?不,不明白。它说不这样执行是自由的。不是说它不能像这样执行。如果我用
System.out.println(“test”)
替换
Thread.sleep()
,怎么样
System.out.println
获得了锁,编译器是否仍然可以“自由”读取
iv.stop
在这种情况下仅一次?@XiangZzz我对线程处理不是很有经验。但我想编译器不会只读取一次。您引用的文档中提到了有关
睡眠
产量
的内容,所以我想这两种方法都得到了编译器的特殊处理。@Sweeper不,它们没有得到编译器的特殊处理这就是重点所在。它们“没有同步语义”。这与您调用的方法的实际功能无关。对于编译器来说,它们只是带一个参数的静态方法调用,它决定执行的任何操作都不太可能受到您调用的特定方法的影响,这不是真的@Rodney。A
Prin