双重检查锁定的无序写入

在针对双重检查锁定方案的无序写入中提到的示例中(参考: IBM文章和维基百科文章 )

在构造函数完全初始化之前,我无法理解为什么Thread1会在同步块中出现的简单原因。 根据我的理解,创建“new”和调用构造函数应该按顺序执行,同步锁不应该释放,直到所有工作都没有完成。

请让我知道我在这里失踪了什么。

构造函数可以完成 – 但这并不意味着该构造函数中涉及的所有写入都已对其他线程可见。 令人讨厌的情况是,在对象的内容变得可见之前, 引用对其他线程可见(因此它们开始使用它)。

你可能会发现Bill Pugh关于它的文章也有助于揭开一些亮点。

就个人而言,我只是避免像瘟疫那样双重检查锁定,而不是试图让它全部工作。

有问题的代码在这里:

public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 if (instance == null) //2 instance = new Singleton(); //3 } } return instance; } 

现在只要你一直认为代码按照它的编写顺序执行,就无法理解这个问题。 即使这样,在对称多处理架构中存在跨多个处理器(或核心)的高速缓存同步问题,这是当今的主流。

例如,Thread1可以将instance引用发布到主内存,但无法在已创建的Singleton对象中发布任何其他数据。 Thread2将以不一致的状态观察对象。

只要Thread2没有进入synchronized块,就不必进行缓存同步,因此Thread2可以无限期地继续运行而不会在一致状态下观察Singleton

当线程1处于// 3时,线程2检查实例是否为空。

 public static Singleton getInstance() { if (instance == null) { synchronized(Singleton.class) { //1 if (instance == null) //2 instance = new Singleton(); //3 } } return instance;//4 } 

此时,内存已经从堆中分配,并且指向它的指针存储在实例引用中,因此线程2执行的“if语句”返回“false”。 请注意,因为当Thread2检查它时实例不为null,所以线程2不会进入同步块,而是返回对“完全构造但部分初始化的Singleton对象”的引用。

代码没有按照它编写的顺序执行,这是一个普遍的问题。 在Java中,线程只有与自身一致。 在具有new一行上创建的instance必须准备好继续下一个。 其他线程没有这样的遗嘱。 例如,如果fieldA为1并且’fieldB’为2,则在线程1上进入此代码:

 fieldA = 5; fieldB = 10; 

和线程2运行此代码:

 int x = fieldA; int y = FieldB; 

xy值为1,2,5和5 10都是预期的,但是在fieldA之前设置和/或拾取了110-fieldB是完全合法的,并且可能也是如此。 因此,双重检查锁定是一个更普遍问题的特例,如果您使用多个线程,则需要了解它,特别是如果它们都访问相同的字段。

应该提到的一个简单的Java 1.5解决方案:标记为volatile字段保证在被引用和写入之后立即从主存储器中读取。 如果上面的fieldAfieldB被声明为volatile ,则xy值为1 10是不可能的。 如果instance是volatile,则双重检查锁定有效。 使用volatile字段需要花费成本,但它不及同步,因此双重检查锁定成为一个非常好的主意。 这是一个更好的主意,因为它可以避免让一堆线程在CPU内核闲置时等待同步。

但你确实想要理解这一点(如果你不能谈论multithreading)。 一方面,您需要避免计时问题,另一方面避免让程序停止,所有线程都等待进入同步块。 这很难理解。