Java 内存模型中的 synchronized 和 volatile 如何工作?

How synchronized and volatile work in Java memory model?

在“有效Java”一书中:

// Broken! - How long would you expect this program to run?
public class StopThread {

    private static boolean stopRequested;

    public static void main(String[] args) throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested)
                    i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        stopRequested = true;
    }
}

backgroundThread 不会在一秒后停止。因为提升,JVM中的优化,HotSpot服务器VM。

您可以在以下主题中查看:
Why HotSpot will optimize the following using hoisting?

优化是这样的:

if (!done)
    while (true)
        i++;

有两种方法可以解决此问题。

1。使用 volatile

private static volatile boolean stopRequested;

volatile的函数是
- 禁止提升
- 它保证任何读取该字段的线程都会看到最近写入的值

2。使用 同步

public class StopThread {

    private static boolean stopRequested;

    private static synchronized void requestStop() {
        stopRequested = true;
    }

    private static synchronized boolean stopRequested() {
        return stopRequested;
    }

    public static void main(String[] args)
                throws InterruptedException {
        Thread backgroundThread = new Thread(new Runnable() {
            public void run() {
                int i = 0;
                while (!stopRequested())
                    i++;
            }
        });
        backgroundThread.start();

        TimeUnit.SECONDS.sleep(1);
        requestStop();
    }
}

上面的代码在Effective Java一书中是正确的,相当于用volatile修饰stopRequested

private static boolean stopRequested() {
    return stopRequested;
}

如果此方法省略 synchronized 关键字,则此程序无法正常运行。
我认为当方法省略 synchronized 关键字时,此更改会导致 提升
是吗?

不是。当同步方法退出时,它会自动与同一对象的同步方法的任何后续调用建立先行关系。 这保证对象状态的改变对所有线程可见。至于你上面的问题, volatilesynchronized 都保证了可见性,使其在 1s 后停止。

要清楚地理解为什么会发生这种情况,您需要了解更深层次上发生的事情。 (这基本上是对所谓的先行关系的解释,我希望这种语言对 reader 来说更稳定)。

通常变量存在于 RAM 内存中。当线程需要使用它们时,它会从 RAM 中取出它们并将它们放入缓存中,以便它可以在需要时尽快访问它们。

使用volatile 强制线程直接从RAM 内存读取和写入变量。因此,当许多线程使用相同的 volatile 变量时,所有线程都看到 RAM 内存中存在的最后一个版本,而不是缓存中可能的旧副本。

当线程进入 synchronized 块时,它需要控制一个监视器变量。所有其他线程等到第一个线程从 synchronized 块退出。为了确保所有线程都能看到相同的修改,同步块中使用的所有变量都是直接从 RAM 内存而不是缓存副本读取和写入的。

因此,如果您尝试在不使用 synchronized 方法或不使用 volatile 关键字的情况下读取变量 stopRequested,您可以读取缓存中存在的可能的旧副本.

要解决这个问题,您需要确保:

  • 所有线程都在使用 volatile 个变量
  • 或者访问这些变量的所有线程都在使用 synchronized 块。

使用方法

private static boolean stopRequested() {
   return stopRequested;
}

没有 synchronized 关键字并且当 stopRequested 不是 volatile 意味着您可以从无效的缓存副本中读取 stopRequested 的值。

声明一个 volatile Java 变量意味着:

此变量的值将永远不会在线程本地缓存:所有读取和写入将直接进入 "main memory";

声明同步意味着:
所以,其中volatile只同步线程内存和"main"内存之间的一个变量的值,synchronized同步线程内存和"main"内存之间的所有变量的值 在方法中并锁定和释放监视器以控制多个线程之间的所有权。