Java同步关键字是否会汇总缓存?
-
25-09-2019 - |
题
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;
}
}
做 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();
}
}
}
假设优化器不会重写代码,如果 instance
没有声明 volatile
当它仍然可以保证时会冲洗到记忆时 synchronized
块已退出,并从内存中读取 synchronized
输入块?
编辑:我忘了使getInstance()静态。我认为这不会改变答案的有效性;你们都知道我的意思。
解决方案
是的, instance
应该宣布 volatile
. 。即使那样,建议不要使用双检查锁定。 IT(或确切地说,Java内存模型)曾经有一个严重的缺陷,允许出版部分实现的对象。这已经在Java5中修复了,仍然是DCL是一个过时的成语,不需要再使用它 - 使用 懒惰的初始化持有人成语 反而。
从 实践中的Java并发, ,第16.2节:
DCL的真正问题是假设,当阅读共享对象引用而无需同步时可能发生的最糟糕的事情是错误地看到一个过时的值(在这种情况下,
null
);在这种情况下,DCL成语通过再次尝试锁定来弥补这一风险。但是最坏的情况实际上更糟 - 可以看到参考的当前值,但对象状态的陈旧值,这意味着可以看出对象处于无效或不正确的状态。JMM(Java 5.0及更高版本)的随后更改使DCL工作 如果
resource
是制造的volatile
, ,而且性能的影响很小,因为volatile
读取通常仅比非易失性读取略贵。但是,这是一个习语,其效用已在很大程度上通过了 - 激励它的力量(缓慢的无害同步,慢慢的JVM启动)不再发挥作用,从而使其作为优化的有效性降低了。懒惰的初始化持有人的成语提供了相同的好处,并且更容易理解。
其他提示
是的,实例需要使用Java中的双检查锁定进行挥发性,因为否则Mysingleton的初始化可能会将部分构造的对象暴露于系统的其余部分。确实,当线程达到“同步”陈述时,线程也会同步,但是在这种情况下为时已晚。
维基百科 还有其他几个堆栈溢出问题对“双重检查锁定”进行了很好的讨论,因此我建议阅读有关它的信息。我还建议不要使用它,除非分析在此特定代码中表现出对性能的真正需求。
有一个更安全,更可读的习语,用于Java Singletons的懒惰初始化:
class Singleton {
private static class Holder {
static final Singleton instance = create();
}
static Singleton getInstance() {
return Holder.instance;
}
private Singleton create() {
⋮
}
private Singleton() { }
}
如果使用更详细的双重检查锁定模式,则必须声明该字段 volatile
, ,正如其他已经指出的那样。
不,它不必是波动的。看 如何解决Java中的“双重检查锁定锁定”声明?
以前的尝试都失败了,因为如果您可以作弊Java并避免挥发性/同步,Java会欺骗您并给您对对象的不正确视图。但是新的 final
语义解决了问题。如果您获得对象引用(通过普通读取),则可以安全地阅读其 final
字段。