我读到了这里-
当线程A写入易失性变量,随后线程B读取该变量时,在写入易失性变量之前对A可见的所有变量的值在读取易失性变量后对B变得可见。因此,从内存可见性的角度来看,编写易失性变量就像退出同步块一样,而读取易失性变量就像进入同步块一样。
下面的片段摘自这里,这篇文章可以追溯到2001年,当时易失性关键字的语义不同。
class SomeClass {
private Resource resource = null;
public Resource getResource() {
if (resource == null) {
synchronized {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
}如果引用是不稳定的,则双检查锁定是固定的。
private volatile Resource resource = null;但是,是否需要将资源类的成员字段设置为易失性的,以确保线程安全?
编辑:
作者在同一篇文章中提到-易失性也不意味着你所想的。
通常建议的非修复方法是将SomeClass的资源字段声明为易失性的。但是,虽然JMM防止对易失性变量的写入相对于另一个变量重新排序,并确保它们被立即刷新到主内存,但它仍然允许对易失性变量的读写进行重新排序。这意味着--除非所有的Resource字段也是不稳定的--线程B仍然可以在资源被设置为引用新创建的资源之后感知构造函数的效果。
这意味着在JDK 5之前,双检查锁定是很好的,考虑到资源字段也是不稳定的,或者类本身是不可变的。请建议一下。
发布于 2017-07-24 11:34:59
在一些可追溯到2001年的旧JVM中,volatile关键字的含义有时会被误解,因此实现的功能并不像它应该的那样--这就是在您的问题中的第二个引用(2001年文章中的一个)中的语句背后的原因--易失性对于DCL来说是一个不修复项。
引用自:5.shtml
从Java 5开始,访问易失性变量创建了一个内存屏障:它有效地同步了所有缓存的变量副本和主内存,就像输入或退出同步对象上的同步块一样。一般来说,这对程序员没有太大的影响,尽管它偶尔会使易失性成为安全发布对象的好选择。如果引用声明为易失性,臭名昭著的双重检查锁定反模式实际上在Java 5中有效。
使用Java 5,事情发生了变化,对volatile字段的写/读不能用非易失性读/写重新排序,所以在代码中声明的所有内容都发生在写之前。根据您的代码片段:如果Resource类的成员字段是不可变的,则不需要使它们成为volatile,就可以让其他线程安全地读取它们。如果其他线程(构造和初始化Resource实例字段的线程除外)可以修改这些memeber字段,那么您需要使它们成为线程安全的(例如。将它们标记为volatile --这只是简单内存障碍的一个例子,可能还不够)。考虑使用volatile的更改的示例
class SomeClass {
private volatile Resource resource = null;
public Resource getResource() {
if (resource == null) {
synchronized {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
}在将实例分配给易失性字段之前,将完全执行Resource()构造函数。读取线程将看到创建实例的线程所写的所有内存-因此所有的非intialization都是可见的,这意味着resource实例的发布是线程安全的。
需要澄清:
@BrianGoetz在2001年发表的一篇文章中指出,将字段标记为易挥发的. ...still允许根据非易失性读写对易失性变量的读写进行重新排序。 不再适用于现代JVM (JVM >= Java5)
免责声明我们在谈到同步时经常使用memory berrier这个术语,但是很多消息来源都说,正是这种障碍阻止了指令的重新排序,所以典型的内存屏障只是确保代码中声明的所有内容都在内存屏障点之前执行(例如。进入同步块,写入易失性变量,在程序到达内存屏障点(例如)之前就真正执行了。您知道,如果读取对易失性变量的引用,它不会在构造函数真正完成并返回表单初始化之前发布)。没有刷新到主存以进行同步??CPU缓存与其他情况一起写入主内存,使多核CPU中的其他线程可以看到该内存的东西是缓存一致性硬件。
https://stackoverflow.com/questions/45279290
复制相似问题