我正在阅读文章 Double-checked locking and the Singleton pattern ,关于双重检查锁定如何被破坏,以及Stack Overflow上的一些相关问题。
我已多次使用此模式/习语,没有任何问题。由于我一直在使用Java 5,我首先想到的是这已经在Java 5内存模型中得到了纠正。然而文章说:
本文引用Java Memory 在为Java修订之前的模型 5.0;关于内存排序的陈述可能不再正确。 然而, 双重检查锁定成语仍然存在 在新的记忆模式下破解。
这是一个真正的问题,如果是这样,在什么条件下?
答案 0 :(得分:3)
同步块的开始保证您可以看到最新数据,但它不保证重新排序,除非您不能期望一致的数据视图 也在同步块中。它不能保证在同步部分内完成的变量修改对其他线程是可见的。只有进入同步块的线程才能保证看到更改。这就是为什么双重检查锁定被打破的原因 - 它在读者方面没有同步。 阅读主题可能会看到单例不为空,但单例数据可能无法完全初始化(可见)。
另一方面,由volatile提供排序,保证排序,例如写入volatile 单例静态字段保证在写入易失性静态字段之前完成对单例对象的写入。它不会阻止创建两个对象的单例;这是通过同步提供的。 类最终静态字段不需要是易失性的。在Java中,JVM负责这个问题。
更多信息可以在:
答案 1 :(得分:2)
有人确定他们的应用程序实际上已经被双重检查锁定失败击中了。实际上,许多使用这种习语的应用程序可能由于各种原因而永远不会遇到问题。
但是,这并不意味着你应该使用它。仅存在不可量化的失败概率应足以说服您不要使用双重检查锁定,尤其是因为有安全的替代方案。
你很幸运。
答案 2 :(得分:2)
我们有一个应用程序使用了一个破碎的复核成语,它在很长一段时间内完美运行 - 事实上,我从来没有遇到过这个成语的问题。当然,无论如何我都修好了。
我猜其中一个原因是线程可见性最终会在现实世界中获得。一旦达到,它就会停留。所以,是的,很难发现问题是否已经发生。
我相信hashCode()
String
的实现部分依赖于这个事实......线程计算hashCode虽然他们没有看到缓存,但最终他们开始看到了。同时,重复计算只意味着浪费CPU时间,并且避免挥发性语义的记忆效应的好处胜过这种浪费的努力(至少这就是为什么他们以这种方式实现它我猜)。有效使用的习语是(实际的String.hashCode()实现):
/** Cache the hash code for the string */
private int hash; // Defaults to 0
public int hashCode() {
int h = hash;
if (h == 0) {
int off = offset;
char val[] = value;
int len = count;
for (int i = 0; i < len; i++) {
h = 31*h + val[off++];
}
hash = h;
}
return h;
}
显然,在使用它之前,必须先思考和测量很多。