双重检查锁定物品

我正在阅读这篇关于“双重检查锁定”的文章,并且在文章的主题之外我想知道为什么在文章的某些方面作者使用下一个成语:

清单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; } 

我的问题是:有没有理由将两次代码同步锁定同一个锁? 有这个任何目的吗?

提前谢谢了。

锁定两次的目的是试图防止无序写入。 内存模型指定了重新排序的位置,部分是根据锁定。 锁定确保在“instance = inst;”之后没有写入(包括单例构造函数中的任何内容)。 线。

然而,为了深入探讨这个话题,我建议Bill Pugh的文章 。 然后永远不要尝试:)

本文引用了5.0之前的Java内存模型(JMM)。 在该模型下,将同步块强制写入主存储器。 因此,它似乎是尝试确保Singleton对象在引用之前被推出。 然而,它并不是很有效,因为写实例可以移动到块中 – 蟑螂汽车旅馆。

但是,5.0之前的模型从未正确实现。 1.4应遵循5.0模型。 类是懒惰地初始化的,所以你不妨写一下

 public static final Singleton instance = new Singleton(); 

或者更好的是,不要使用单身人士,因为他们是邪恶的。

Jon Skeet是对的:阅读Bill Pugh的文章。 汉斯使用的习语是不起作用的精确forms, 应该使用。

这是不安全的:

 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; } 

除非您每秒检索此对象数十亿次,否则实际上的性能损失可以忽略不计。

遵循John Skeet建议:

然而,为了深入探讨这个话题,我建议Bill Pugh的文章。 然后永远不要尝试:)

这是第二个同步块的关键:

此代码将Helper对象的构造放在内部同步块中。 这里的直观想法是,在释放同步的位置应该有一个内存屏障,这应该可以防止重新排序Helper对象的初始化和对字段助手的赋值。

所以基本上,使用Inner同步块,我们试图“欺骗”JMM在同步块内创建实例,以强制JMM在同步块完成之前执行该分配。 但是这里的问题是JMM正在引导我们,正在移动同步块内同步块之前的分配,将我们的问题移回到beginnig。

这是我从这些文章中理解的,非常有趣,再次感谢回复。

好吧,但文章说

清单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”之后没有写入的点还没有完成?

我现在将阅读这篇文章,感谢您的链接。

从Java 5开始,您可以通过声明字段volatile来进行双重检查锁定工作。

有关完整说明,请参见http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html 。

关于这个成语,有一篇非常明智和澄清的文章:

http://www.javaworld.com/javaworld/jw-02-2001/jw-0209-double.html?page=1

另一方面,我认为dhighwayman.myopenid的含义是为什么编写器将一个同步块引用到引用同一个类的另一个同步块中的同一个类(synchronized(Singleton.class))。 它可能发生在一个新的实例(Singleton inst = instance;)在该块中创建并保证它是线程安全的,因此需要编写另一个synchronized。

否则,我看不出任何意义。

请参阅Java内存模型上的Google Tech Talk,以获得对JMM更精细点的精彩介绍。 由于缺少这里,我还想指出Jeremy Mansons的博客“Java Concurrency” esp。 关于Double Checked锁定的post(Java世界中任何人都有关于此问题的文章:)。

对于Java 5及更好的实际上,有一个双重检查变体可能比同步整个访问器更好。 双重锁定声明中也提到了这一点:

 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或更低版本中都不起作用。