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

时间:2016-10-08 07:39:37

标签: java synchronized volatile 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;
    }
}

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。使用 synchronized

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关键字时,此更改会导致提升
是吗?

3 个答案:

答案 0 :(得分:3)

要明确理解为什么会发生这种情况,您需要了解更深层次发生的事情。 (这基本上是对所谓之前发生的关系的解释,我希望这种语言对于读者而言更为明白。)

通常,RAM存储器中存在变量。当一个线程需要使用它们时,它会从RAM中取出它们并将它们放在缓存中,这样它就可以尽快访问它们直到需要它为止。

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

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

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

要解决此问题,您需要确保:

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

使用方法

private static boolean stopRequested() {
   return stopRequested;
}

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

答案 1 :(得分:0)

不是。当synchronized方法退出时,它会自动与同一对象的同步方法的任何后续调用建立before-before关系。 这可以保证对所有线程都可以看到对象状态的更改。至于上面的问题,volatilesynchronized都保证了在1秒后停止的可见性。

答案 2 :(得分:0)

声明易失性Java变量意味着:

此变量的值永远不会在线程本地缓存:所有读写都将直接进入“主内存” ;

声明同步意味着:
因此,如果volatile仅在线程内存和“主”内存之间同步一个变量的值,则在方法和锁中,同步将线程内存和“主”内存之间的所有变量的值同步。并释放一个监视器来控制多个线程之间的所有权。