在“ 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++;
有两种方法可以解决问题。
private static volatile boolean stopRequested;
volatile 的功能是
- 禁止吊装
- 它保证读取该字段的任何线程都将看到最近写入的值
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
关键字时,此更改会导致提升。
是吗?
答案 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关系。 这可以保证对所有线程都可以看到对象状态的更改。至于上面的问题,volatile
和synchronized
都保证了在1秒后停止的可见性。
答案 2 :(得分:0)
声明易失性Java变量意味着:
此变量的值永远不会在线程本地缓存:所有读写都将直接进入“主内存” ;
声明同步意味着:
因此,如果volatile仅在线程内存和“主”内存之间同步一个变量的值,则在方法和锁中,同步将线程内存和“主”内存之间的所有变量的值同步。并释放一个监视器来控制多个线程之间的所有权。