双重检查的锁定条

时间:2020-03-06 14:58:20  来源:igfitidea点击:

我正在阅读有关"双重检查的锁定"的这篇文章,而在本文的主要主题之外,我想知道为什么作者在本文中的某些地方使用了下一个惯用法:

Listing 7. Attempting to solve the out-of-order write problem  

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;"之后不会发生任何写操作(包括在singleton构造函数中的写操作)。线。

但是,要更深入地研究该主题,我将推荐比尔·普格(Bill Pugh)的文章。然后再也不要尝试:)

好的,但是文章说

The code in Listing 7 doesn't work because of the current definition of the memory model. The Java Language Specification (JLS) demands that code within a synchronized block not be moved out of a synchronized block. However, it does not say that code not in a synchronized block cannot be moved into a synchronized block.

而且似乎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"之后没有写的点还没有实现?

我现在将阅读该文章,感谢链接。

乔恩·斯基特(Jon Skeet)是对的:请阅读比尔·普格(Bill Pugh)的文章。汉斯使用的成语是无法使用的精确形式,不应使用。

这是不安全的:

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

除非我们每秒检索该对象数以十亿计次,否则实际上对性能的影响是微不足道的。

本文引用了5.0之前的Java内存模型(JMM)。在该模型下,留下一个同步块强制写入主存储器。因此,这似乎是在尝试确保在引用该对象之前将Singleton对象推出。但是,这并不是很有效,因为可以将对实例的写操作上移到蟑螂汽车旅馆的街区。

但是,5.0之前的模型从未正确实现。 1.4应该遵循5.0模型。类是懒惰地初始化的,所以我们最好还是写

public static final Singleton instance = new Singleton();

或者更妙的是,不要使用单例,因为它们很邪恶。

从Java 5开始,我们可以通过声明字段易失性来进行双重检查锁定。

有关完整说明,请参见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;)时,可能会发生这种情况,并且为了确保它是线程安全的,有必要编写另一个同步的实例。

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

遵循John Skeet的建议:

However, to go deeper into the subject
  I'd recommend Bill Pugh's article. And
  then never attempt it :)

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

This code puts construction of the
  Helper object inside an inner
  synchronized block. The intuitive idea
  here is that there should be a memory
  barrier at the point where
  synchronization is released, and that
  should prevent the reordering of the
  initialization of the Helper object
  and the assignment to the field
  helper.

因此,基本上,对于内部同步块,我们尝试"欺骗" JMM,以便在同步块内部创建实例,以强制JMM在同步块完成之前执行该分配。但是这里的问题是,JMM正在带领我们前进,并且正在将同步块内部同步块之前的分配移动到同步块中,从而将我们的问题移回了开始。

这是我从那些文章中了解到的,真的很有趣,再次感谢答复。

我在这里介绍了很多:

http://tech.puredanger.com/2007/06/15/double-checked-locking/

请参阅有关Java内存模型的Google技术讲座,对JMM的精要之处进行非常好的介绍。由于此处缺少该字段,因此我还要指出Jeremy Mansons博客" Java Concurrency"特别是。有关Double Checked锁定的文章(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或者更低版本中也将不起作用。