首页 > 解决方案 > Thread.yield 是否保证刷新对内存的读/写?

问题描述

让我们保存一下,我有这段代码,它展示了一个线程读取过时的缓存,这会阻止它退出它的 while 循环。

class MyRunnable implements Runnable {
    boolean keepGoing = true; // volatile fixes visibility
    @Override public void run() {
        while ( keepGoing ) {
            // synchronized (this) { } // fixes visibility
            // Thread.yield(); // fixes visibility
            System.out.println(); // fixes visibility 
        }
    }
}
class Example {
    public static void main(String[] args) throws InterruptedException{
        MyRunnable myRunnable = new MyRunnable();
        new Thread(myRunnable).start();
        Thread.sleep(100);  
        myRunnable.keepGoing = false;
    }
}

我相信 Java 内存模型保证了对 volatile 变量的所有写入与来自任何线程的所有后续读取同步,从而解决了问题。

如果我的理解是正确的,同步块生成的代码也会清除所有挂起的读取和写入,这充当一种“内存屏障”并解决了问题。

从实践中我已经看到插入yield并且println还使变量更改对线程可见并且它正确退出。我的问题是:

yield/println/io 是由 JMM 以某种方式保证的内存屏障,还是不能保证有效的幸运副作用?


编辑:至少我在这个问题的措辞中做出的一些假设是错误的,例如关于同步块的假设。我鼓励该问题的读者阅读下面发布的答案中的更正。

标签: javamultithreadingcachingjava-memory-model

解决方案


不,JLS 或 javadocs不能保证您在那里使用的类或方法。

当前的实现中,实际上 在和中存在内存屏障。(如果您要深入研究实现代码,您应该能够弄清楚它们是如何产生的以及它们的用途。)yield()println

但是,不能保证所有平台上的所有 Java 1实现都存在这些内存屏障。规范没有指定发生关系存在2之前,因此它们不需要插入3 个内存屏障。

假设:

  • 假设它Thread.yield()被实现为no-op。(以同样的方式System.gc()可以成为无操作。)

  • 假设输出流堆栈经过优化,其内部不再需要同步。例如,假设JVM可以推断出特定的输出流是线程受限的,并且在写入其缓冲区时不需要内存屏障。

现在我个人认为这些变化不太可能发生。(而且它们甚至可能不可行。)但是如果它们确实发生了,那么目前依赖于那些偶然的内存屏障的相当多的“损坏”的应用程序很可能会停止工作。

关键是:如果您想要保证,请依赖规格说明。规范是唯一真正的保证……如果您的代码需要可移植。


1 - 特别是未来的。
2 - 事实上,正如 Holger 的回答所解释的,javadocsThread明确指出您不能假设或依赖于yield(). 这显然意味着在与任何其他线程上的任何操作之间之前没有发生任何事情。 3 - 内存屏障实际上是​​一个实现细节。典型的编译器使用它们来实现 JMM 的可见性保证。关键是保证,而不是用于实现它们的策略。因此,当您试图确定多线程代码是否正确时,任何关于内存屏障、缓存、寄存器等的讨论都是无关紧要的。yield()


推荐阅读