我正在阅读this article关于“Double-Checked locking”的内容,在文章的主题之外,我想知道为什么在文章的某些方面作者使用下一个成语:
清单7.尝试解决无序写入问题
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 Singleton inst = instance; //2 if (inst == null) { synchronized(Singleton.class) { //3 inst = new Singleton(); //4 } instance = inst; //5 } } } return instance; }
我的问题是: 有没有理由用同一个锁同步两次代码? 有这个任何目的吗?
非常感谢提前。
答案 0 :(得分:15)
锁定两次的目的是尝试以防止无序写入。内存模型指定了重新排序的位置,部分是根据锁定。锁定确保在“instance = inst;”之后没有写入(包括单例构造函数中的任何内容)。线。
然而,为了深入研究这个主题,我建议Bill Pugh's article。然后永远不要尝试:)
答案 1 :(得分:13)
本文引用了5.0之前的Java内存模型(JMM)。在该模型下,将同步块强制写入主存储器。因此,它似乎是尝试确保Singleton对象在引用之前被推出。但是,它并不是很有效,因为实例写入可以移动到块中 - 蟑螂汽车旅馆。
然而,5.0之前的模型从未正确实现。 1.4应遵循5.0模型。类是懒惰地初始化的,所以你不妨写一下
public static final Singleton instance = new Singleton();
或者更好的是,不要使用单身人士,因为他们是邪恶的。
答案 2 :(得分:6)
Jon Skeet说得对:阅读Bill Pugh's文章。汉斯使用的习语是不起作用的精确形式,不应该使用。
这是不安全的:
private static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
这也不安全:
public static Singleton getInstance()
{
if (instance == null)
{
synchronized(Singleton.class) { //1
Singleton inst = instance; //2
if (inst == null)
{
synchronized(Singleton.class) { //3
inst = new Singleton(); //4
}
instance = inst; //5
}
}
}
return instance;
}
不要做任何一件事。
相反,同步整个方法:
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
除非您每秒检索此对象数十亿次,否则实际效果可以忽略不计。
答案 3 :(得分:3)
答案 4 :(得分:1)
遵循John Skeet建议:
但是,要深入研究这个主题 我推荐Bill Pugh的文章。和 然后永远不要尝试:)
这是第二个同步块的关键:
这段代码建了 辅助对象在内部 同步块。直观的想法 这里应该有记忆 屏障在哪里 同步被释放,那 应该防止重新排序 Helper对象的初始化 以及对该领域的分配 帮手。
所以基本上,使用内部同步块,我们试图“欺骗”JMM在同步块内创建实例,以强制JMM在同步块完成之前执行该分配。但是这里的问题是JMM正在引导我们,正在移动同步块内同步块之前的分配,将我们的问题移回到beginnig。
这是我从那些文章中理解的,非常有趣,再一次感谢回复。
答案 5 :(得分:0)
好吧,但文章说
由于内存模型的当前定义,清单7中的代码不起作用。 Java语言规范(JLS)要求同步块中的代码不能移出同步块。但是,它并没有说不在同步块中的代码不能移动到同步块中。
似乎JVM在ASM中进行下一次转换为“伪代码”:
public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 Singleton inst = instance; //2 if (inst == null) { synchronized(Singleton.class) { //3 //inst = new Singleton(); //4 instance = new Singleton(); } //instance = inst; //5 } } } return instance; }
到目前为止,“instance = inst”之后没有写入的意义没有完成?
我现在将阅读这篇文章,感谢您的链接。
答案 6 :(得分:0)
从Java 5开始,您可以通过声明字段volatile来进行双重检查锁定。
有关完整说明,请参阅http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html。
答案 7 :(得分:0)
关于这个成语,有一篇非常明智和澄清的文章:
http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html?page=1
另一方面,我认为dhighwayman.myopenid的含义是为什么编写器将一个同步块引用到引用同一个类的另一个同步块中的同一个类(synchronized(Singleton.class))。它可能会在该块中创建一个新实例(Singleton inst = instance;)并保证它是线程安全的,因此需要编写另一个同步。
否则,我看不出任何意义。
答案 8 :(得分:0)
请参阅Java Memory Model上的Google Tech Talk,了解JMM更精细的内容。由于在这里缺少,我还想指出Jeremy Mansons的博客'Java Concurrency'。 Double Checked locking上的帖子(Java世界中任何人都有关于此的文章:)。
答案 9 :(得分:0)
对于Java 5及更好的实际上,有一个双重检查变体可能比同步整个访问器更好。 Double-Checked Locking Declaration:
中也提到了这一点class Foo {
private volatile Helper helper = null;
public Helper getHelper() {
if (helper == null) {
synchronized(this) {
if (helper == null)
helper = new Helper();
}
}
return helper;
}
}
这里的关键区别是在变量声明中使用 volatile - 否则它不起作用,无论如何它在Java 1.4或更低版本中都不起作用。