Java 内存模型中的同步和易失性如何工作?

Edw*_*ard 4 java volatile synchronized java-memory-model

在《Effective 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;
    }
}
Run Code Online (Sandbox Code Playgroud)

背景线程不会在一秒后停止。因为JVM、HotSpot服务器VM中的提升、优化都是如此。

您可以在以下主题中查看这一点:
为什么 HotSpot 将使用提升来优化以下内容?

优化过程是这样的:

if (!done)
    while (true)
        i++;
Run Code Online (Sandbox Code Playgroud)

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

1.使用易失性

private static volatile boolean stopRequested;
Run Code Online (Sandbox Code Playgroud)

易失性的作用是
——禁止提升
——它保证任何读取该字段的线程都会看到最近写入的值

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

上面的代码就在Effective Java一书中,相当于用来volatile装饰stopRequested.

private static boolean stopRequested() {
    return stopRequested;
}
Run Code Online (Sandbox Code Playgroud)

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

Dav*_*INO 5

为了清楚地理解为什么会发生这种情况,您需要了解更深层次上发生的事情。(这基本上是对所谓的“发生之前”关系的解释,我希望使用一种对读者来说更容易理解的语言)。

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

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

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

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

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

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

使用方法

private static boolean stopRequested() {
   return stopRequested;
}
Run Code Online (Sandbox Code Playgroud)

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