双重检查锁定的无序写入

时间:2012-06-25 18:47:23

标签: java multithreading synchronization jls double-checked-locking

在针对双重检查锁定方案的无序写入中提到的示例中(参考: IBM article& Wikipedia Article

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

请告诉我这里缺少的东西。

4 个答案:

答案 0 :(得分:12)

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

你可能会发现Bill Pugh's article on it也有助于提供一些亮点。

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

答案 1 :(得分:1)

当线程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不会进入synchronized块,而是返回对“完全构造但部分初始化的Singleton对象”的引用。

答案 2 :(得分:1)

有问题的代码在这里:

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。 / p>

答案 3 :(得分:0)

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

fieldA = 5;
fieldB = 10;

并且线程2运行此代码:

int x = fieldA;
int y = FieldB;

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

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

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