11

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

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

我的问题是:有什么理由用同一个锁同步两次代码吗?这有什么目的吗?

提前谢谢了。

4

10 回答 10

15

锁定两次的目的是为了防止乱序写入。内存模型指定了可能发生重新排序的位置,部分是根据锁。锁确保在“instance = inst;”之后不会出现任何写入(包括单例构造函数中的任何写入)。线。

但是,要更深入地了解该主题,我建议您阅读 Bill Pugh 的文章。然后永远不要尝试它:)

于 2008-10-01T11:41:26.570 回答
13

这篇文章指的是 5.0 之前的 Java 内存模型 (JMM)。在该模型下,将同步块强制写入主存储器。因此,这似乎是在尝试确保 Singleton 对象在对其的引用之前被推出。但是,它并不能很好地工作,因为对实例的写入可以向上移动到块中 - 蟑螂汽车旅馆。

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

public static final Singleton instance = new Singleton();

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

于 2008-10-01T12:38:00.427 回答
6

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

除非您每秒无数次地检索此对象,否则实际上对性能的影响可以忽略不计。

于 2008-10-01T12:07:36.727 回答
3

我在这里介绍了很多:

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

于 2008-10-01T15:44:39.933 回答
1

遵循约翰斯基特建议:

然而,为了更深入地研究这个主题,我推荐 Bill Pugh 的文章。然后永远不要尝试它:)

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

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

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

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

于 2008-10-01T13:48:37.233 回答
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”之后的不写点还没有实现?

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

于 2008-10-01T11:51:20.120 回答
0

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

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

于 2008-10-01T12:44:03.720 回答
0

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

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

另一方面,我认为 dhighwayman.myopenid 的意思是为什么作者将一个引用同一类的同步块 (synchronized(Singleton.class)) 放在另一个引用同一类的同步块中。它可能会在该块中创建一个新实例(Singleton inst = instance;)时发生,并且为了保证它是线程安全的,有必要编写另一个同步的。

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

于 2008-10-01T13:22:48.307 回答
0

请参阅有关Java 内存模型的 Google 技术讲座,以获得对 JMM 更精细点的非常好的介绍。由于这里缺少它,我还想指出 Jeremy Mansons 的博客'Java Concurrency'尤其是。关于双重检查锁定的帖子(Java 世界中的任何人似乎都有关于此的文章 :)。

于 2008-12-27T16:52:43.673 回答
0

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

于 2010-01-25T11:32:13.590 回答