[英]Need of volatile keyword in case of DCL
我只是在实践中阅读并发性。 我知道有必要在字段的双重检查锁定机制中使用volatile关键字,否则线程可以读取非null对象的陈旧值。 因为不使用volatile关键字就可以对指令重新排序。 因此,在调用构造函数之前,可以将引用分配给资源变量。 因此线程可以看到部分构造的对象。
我对此有疑问。
我认为同步块也限制了编译器对指令的重新排序,所以为什么我们在这里需要volatile关键字?
public class DoubleCheckedLocking {
private static volatile Resource resource;
public static Resource getInstance() {
if (resource == null) {
synchronized (DoubleCheckedLocking.class) {
if (resource == null)
resource = new Resource();
}
}
return resource;
}
}
如果调用线程(T1)也从同步块(在同一锁上)读取线程,则JMM仅保证线程T1将在同步块内看到由另一个线程T2创建的正确初始化的对象。
由于T1可以看到资源不为null,因此无需经过同步块就可以立即返回它,因此它可以获取一个对象,但看不到其状态正确初始化 。
使用volatile可以带来保证,因为在volatile字段的写入与该volatile字段的读取之间存在事前发生的关系。
正如其他人所观察到的那样,在这种情况下必须具有可变性,因为在首次访问资源时可能发生数据争用。 在没有volatile
,无法保证读取非空值的线程A
实际上会访问完全初始化的资源-如果同时在同步部分的线程B
中构建了该线程A,尚未达到。 然后,线程A
可以尝试使用半初始化的副本。
从JSR-133(2004)开始使用时 ,仍然不建议使用volatile进行双重检查锁定,因为它不易读,并且不如推荐的替代方法有效 :
private static class LazyResourceHolder {
public static Resource resource = new Resource();
}
...
public static Resource getInstance() {
return LazyResourceHolder.something;
}
这是按需初始化Holder Class习惯用法,根据上一页,
[...]它的线程安全性来自以下事实:保证属于类初始化的一部分的操作(例如静态初始化程序)对于使用该类的所有线程都是可见的;其惰性初始化是由于内部类的事实在某些线程引用其字段或方法之一之前不会加载。
实际上,这里不需要使用volatile
。 使用volatile
将意味着每次在线程方法中使用实例变量时都会有多个线程,这不会优化读取的内存,但要确保一次又一次地读取它。 我唯一使用volatile
是在线程中有停止指示符的地方( private volatile boolean stop = false;
)
像您的示例代码中那样创建单例会变得非常复杂,并且不会真正提高速度。 JIT编译器非常擅长进行线程锁定优化。
使用以下方法创建单例会更好:
public static synchronized Resource getInstance() {
if (resource == null) {
resource = new Resource();
}
return resource;
}
这更容易阅读并推断其对人类的逻辑。
另请参见您是否曾经在Java中使用volatile关键字? ,而volatile
实际上通常用于线程中的某些循环结束标志。
声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.