系统相关
首页 > 系统相关> > Java内存模型中的同步和易失性如何工作?

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

作者:互联网

在“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.使用挥发性

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

解决方法:

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

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

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

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

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

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

>所有线程都使用volatile变量
>或访问这些变量的所有线程都使用同步块.

使用方法

private static boolean stopRequested() {
   return stopRequested;
}

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

标签:java,synchronized,volatile,java-memory-model
来源: https://codeday.me/bug/20190627/1309681.html