Mar*_*ton 6 java volatile synchronized double-checked-locking
仅限Java 5及以上版本.假设一个多处理器共享内存计算机(你现在可能正在使用它).
这是一个单例的延迟初始化代码:
public final class MySingleton {
private static MySingleton instance = null;
private MySingleton() { }
public static MySingleton getInstance() {
if (instance == null) {
synchronized (MySingleton.class) {
if (instance == null) {
instance = new MySingleton();
}
}
}
return instance;
}
}
Run Code Online (Sandbox Code Playgroud)
是否instance必须声明volatile,以阻止优化重写的getInstance()如下(这将是一个连续的程序是正确的):
public static MySingleton getInstance() {
if (instance == null) {
synchronized (MySingleton.class) {
// instance must be null or we wouldn't be here (WRONG!)
instance = new MySingleton();
}
}
}
Run Code Online (Sandbox Code Playgroud)
假设优化器没有重写代码,如果instance没有声明volatile,是否仍然保证在synchronized退出块时刷新到内存,并在synchronized进入块时从内存中读取?
编辑:我忘了让getInstance()静态.我不认为这会改变答案的有效性; 你们都知道我的意思.
Pét*_*rök 15
是的,instance应该宣布volatile.即便如此,建议不要使用双重检查锁定.它(或者确切地说,Java内存模型)曾经有一个严重的缺陷,允许发布部分实现的对象.这已在Java5中得到修复,DCL仍然是一个过时的习惯用语,不再需要使用它 - 使用懒惰的初始化持有者习惯用法.
从Java Concurrency in Practice,第16.2节:
DCL的真正问题是假设在没有同步的情况下读取共享对象引用时可能发生的最糟糕的事情是错误地看到陈旧值(在这种情况下
null); 在这种情况下,DCL惯用法通过再次尝试锁定来补偿这种风险.但最坏的情况实际上要差得多 - 有可能看到参考的当前值但是对象状态的陈旧值,这意味着可以看到对象处于无效或不正确的状态.在JMM(Java 5.0和更高版本)的后续变化使DCL工作如果
resource由volatile,而这对性能的影响很小,因为volatile读取通常只稍贵比非易失性读取.然而,这是一个习惯已经基本通过的习惯用语 - 激发它的力量(缓慢的无竞争同步,慢速JVM启动)不再起作用,使其作为优化效率降低.懒惰的初始化持有者习语提供了相同的好处,并且更容易理解.