双重检查锁定

来源:互联网 发布:五灯会元 知乎 编辑:程序博客网 时间:2024/06/10 07:25

http://www.ibm.com/developerworks/cn/java/j-dcl.html

单例创建模式是一个通用的编程习语。和多线程一起使用时,必需使用某种类型的同步。在努力创建更有效的代码时,Java 程序员们创建了双重检查锁定习语,将其和单例创建模式一起使用,从而限制同步代码量。然而,由于一些不太常见的 Java 内存模型细节的原因,并不能保证这个双重检查锁定习语有效。它偶尔会失败,而不是总失败。此外,它失败的原因并不明显,还包含 Java 内存模型的一些隐秘细节。这些事实将导致代码失败,原因是双重检查锁定难于跟踪。在本文余下的部分里,我们将详细介绍双重检查锁定习语,从而理解它在何处失效。

单例创建习语

要理解双重检查锁定习语是从哪里起源的,就必须理解通用单例创建习语,如清单 1 中的阐释:

清单 1. 单例创建习语import java.util.*;class Singleton{  private static Singleton instance;  private Vector v;  private boolean inUse;  private Singleton()  {    v = new Vector();    v.addElement(new Object());    inUse = true;  }  public static Singleton getInstance()  {    if (instance == null)          //1      instance = new Singleton();  //2    return instance;               //3  }}

此类的设计确保只创建一个 Singleton 对象。构造函数被声明为 privategetInstance() 方法只创建一个对象。这个实现适合于单线程程序。然而,当引入多线程时,就必须通过同步来保护getInstance() 方法。如果不保护 getInstance() 方法,则可能返回 Singleton 对象的两个不同的实例。假设两个线程并发调用getInstance() 方法并且按以下顺序执行调用:

  1. 线程 1 调用 getInstance() 方法并决定 instance 在 //1 处为 null

  2. 线程 1 进入 if 代码块,但在执行 //2 处的代码行时被线程 2 预占。

  3. 线程 2 调用 getInstance() 方法并在 //1 处决定 instancenull

  4. 线程 2 进入 if 代码块并创建一个新的 Singleton 对象并在 //2 处将变量 instance 分配给这个新对象。

  5. 线程 2 在 //3 处返回 Singleton 对象引用。

  6. 线程 2 被线程 1 预占。

  7. 线程 1 在它停止的地方启动,并执行 //2 代码行,这导致创建另一个 Singleton 对象。

  8. 线程 1 在 //3 处返回这个对象。

结果是 getInstance() 方法创建了两个 Singleton 对象,而它本该只创建一个对象。通过同步getInstance() 方法从而在同一时间只允许一个线程执行代码,这个问题得以改正,如清单 2 所示:

清单 2. 线程安全的 getInstance() 方法public static synchronized Singleton getInstance(){  if (instance == null)          //1    instance = new Singleton();  //2  return instance;               //3}

清单 2 中的代码针对多线程访问 getInstance() 方法运行得很好。然而,当分析这段代码时,您会意识到只有在第一次调用方法时才需要同步。由于只有第一次调用执行了 //2 处的代码,而只有此行代码需要同步,因此就无需对后续调用使用同步。所有其他调用用于决定instance 是非 null 的,并将其返回。多线程能够安全并发地执行除第一次调用外的所有调用。尽管如此,由于该方法是synchronized 的,需要为该方法的每一次调用付出同步的代价,即使只有第一次调用需要同步。

为使此方法更为有效,一个被称为双重检查锁定的习语就应运而生了。这个想法是为了避免对除第一次调用外的所有调用都实行同步的昂贵代价。同步的代价在不同的 JVM 间是不同的。在早期,代价相当高。随着更高级的 JVM 的出现,同步的代价降低了,但出入synchronized 方法或块仍然有性能损失。不考虑 JVM 技术的进步,程序员们绝不想不必要地浪费处理时间。

因为只有清单 2 中的 //2 行需要同步,我们可以只将其包装到一个同步块中,如清单 3 所示:

清单 3. getInstance() 方法public static Singleton getInstance(){  if (instance == null)  {    synchronized(Singleton.class) {      instance = new Singleton();    }  }  return instance;}

清单 3 中的代码展示了用多线程加以说明的和清单 1 相同的问题。当 instancenull 时,两个线程可以并发地进入if 语句内部。然后,一个线程进入 synchronized 块来初始化 instance,而另一个线程则被阻断。当第一个线程退出synchronized 块时,等待着的线程进入并创建另一个 Singleton 对象。注意:当第二个线程进入synchronized 块时,它并没有检查 instance 是否非 null


双重检查锁定

为处理清单 3 中的问题,我们需要对 instance 进行第二次检查。这就是“双重检查锁定”名称的由来。将双重检查锁定习语应用到清单 3 的结果就是清单 4 。

清单 4. 双重检查锁定示例public static Singleton getInstance(){  if (instance == null)  {    synchronized(Singleton.class) {  //1      if (instance == null)          //2        instance = new Singleton();  //3    }  }  return instance;}

双重检查锁定背后的理论是:在 //2 处的第二次检查使(如清单 3 中那样)创建两个不同的 Singleton 对象成为不可能。假设有下列事件序列:

  1. 线程 1 进入 getInstance() 方法。

  2. 由于 instancenull,线程 1 在 //1 处进入 synchronized 块。

  3. 线程 1 被线程 2 预占。

  4. 线程 2 进入 getInstance() 方法。

  5. 由于 instance 仍旧为 null,线程 2 试图获取 //1 处的锁。然而,由于线程 1 持有该锁,线程 2 在 //1 处阻塞。

  6. 线程 2 被线程 1 预占。

  7. 线程 1 执行,由于在 //2 处实例仍旧为 null,线程 1 还创建一个 Singleton 对象并将其引用赋值给instance

  8. 线程 1 退出 synchronized 块并从 getInstance() 方法返回实例。

  9. 线程 1 被线程 2 预占。

  10. 线程 2 获取 //1 处的锁并检查 instance 是否为 null

  11. 由于 instance 是非 null 的,并没有创建第二个 Singleton 对象,由线程 1 创建的对象被返回。

双重检查锁定背后的理论是完美的。不幸地是,现实完全不同。双重检查锁定的问题是:并不能保证它会在单处理器或多处理器计算机上顺利运行。

双重检查锁定失败的问题并不归咎于 JVM 中的实现 bug,而是归咎于 Java 平台内存模型。内存模型允许所谓的“无序写入”,这也是这些习语失败的一个主要原因。




原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 3岁宝宝脾气不好怎么办 脾气不好吓到宝宝了怎么办 四个月宝宝脾气不好怎么办 5岁说话不清楚该怎么办 小孩快上幼儿园了不怎么说话怎么办 四岁宝宝吐字不清楚怎么办 孕28周胎儿腿短怎么办 b超显示腿短怎么办 2岁多宝宝不愿意说话怎么办 6岁儿童咬字不清怎么办 两岁宝宝爱看电视怎么办 两岁宝宝喜欢看电视怎么办 三岁宝宝说话不清晰怎么办 儿子快四岁了说话不清楚怎么办 儿子快三岁了说话不清楚怎么办 2岁宝宝受刺激了怎么办 一岁的宝宝口臭怎么办 古话说小孩牙齿掉了怎么办 宝宝三岁胆子小怎么办 小宝宝就是不愿意学说话怎么办 2岁宝宝体重偏重怎么办 三岁宝宝体重偏重怎么办 3岁宝宝说话晚怎么办 孩子吃饭总说话吃饭慢怎么办 我的孩子不聪明怎么办 小孩个子长不高怎么办 晚修自己老喜欢讲话怎么办 宝2岁宝脾气大怎么办 宝宝比同龄孩子发育晚怎么办 早教机构业绩不好怎么办 孩子上早教课哭怎么办 两岁宝宝哭闹不止怎么办 7个月宝宝怕生疏怎么办 5个月宝宝怕洗澡怎么办 4个月宝宝认人怎么办 2岁宝宝语言迟缓怎么办 宝宝老是哭闹不睡觉怎么办 7岁儿童说话晚怎么办 一岁多的婴儿说话晚该怎么办 宝宝甲低300多怎么办 两岁半宝宝还不怎么会讲话怎么办