双重检查锁定在延迟初始化的单例模式中见得比较多(单例模式实现方式很多,这里为说明双重检查锁定问题,只选取这一种方式),先来看一个版本:

public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}

上面是最原始的模式,一眼就可以看出,在多线程环境下,可能会产生多个Singleton实例,于是有了其同步的版本:

public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public synchronized static Singleton getInstance() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}

在这个版本中,每次调用getInstance都需要取得Singleton.class上的锁,然而该锁只是在开始构建Singleton 对象的时候才是必要的,后续的多线程访问,效率会降低,于是有了接下来的版本:

public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

很好的想法!不幸的是,该方案也未能解决问题之根本:

原因在于:初始化Singleton 和 将对象地址写到instance字段 的顺序是不确定的。在某个线程new Singleton()时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。

此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化;此时若另外一个线程来调用getInstance,取到的就是状态不正确的对象。

鉴于以上原因,有人可能提出下列解决方案:

public class Singleton {
private static Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if(instance == null) {
Singleton temp;
synchronized(Singleton.class) {
temp = instance;
if(temp == null) {
synchronized(Singleton.class) {
temp = new Singleton();
}
instance = temp;
}
}
}
return instance;
}
}

该方案将Singleton对象的构造置于最里面的同步块,这种思想是在退出该同步块时设置一个内存屏障,以阻止初始化Singleton 和 将对象地址写到instance字段 的重新排序。

不幸的是,这种想法也是错误的,同步的规则不是这样的。退出监视器(退出同步)的规则是:所以在退出监视器前面的动作都必须在释放监视器之前完成。然而,并没有规定说退出监视器之后的动作不能放到退出监视器之前完成。也就是说同步块里的代码必须在退出同步时完成,而同步块后面的代码则可以被编译器或运行时环境移到同步块中执行。

编译器可以合法的,也是合理的,将instance = temp移动到最里层的同步块内,这样就出现了上个版本同样的问题。

在JDK1.5及其后续版本中,扩充了volatile语义,系统将不允许对 写入一个volatile变量的操作与其之前的任何读写操作 重新排序,也不允许将 读取一个volatile变量的操作与其之后的任何读写操作 重新排序。

在jdk1.5及其后的版本中,可以将instance 设置成volatile以让双重检查锁定生效,如下:

public class Singleton {
private static volatile Singleton instance = null;
private Singleton(){}
public static Singleton getInstance() {
if(instance == null) {
synchronized(Singleton.class) {
if(instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}

The "Double-Checked Locking is Broken" Declaration的更多相关文章

  1. Java中的双重检查锁(double checked locking)

    最初的代码 在最近的项目中,写出了这样的一段代码 private static SomeClass instance; public SomeClass getInstance() { if (nul ...

  2. Double Checked Locking 模式

    转自:http://blog.csdn.net/wwsoon/article/details/1485886 之前在使用Double Check Locking 模式时,发现自己还是不太理解.于是写个 ...

  3. Double Check Locking 双检查锁机制

    方法保证了多线程并发下的线程安全性.这里在声明变量时使用了volatile关键字来保证其线程间的可见性:在同步代码块中使用二次检查,以保证其不被重复实例化.集合其二者,这种实现方式既保证了其高效性,也 ...

  4. JMM(java内存模型)

    What is a memory model, anyway? In multiprocessorsystems, processors generally have one or more laye ...

  5. Java内存模型(JSR133)问与答

    What is a memory model, anyway? In multiprocessor systems, processors generally have one or more lay ...

  6. 阿里巴巴Java开发手册-并发处理

    1. [强制]获取单例对象需要保证线程安全,其中的方法也要保证线程安全.说明:资源驱动类.工具类.单例工厂类都需要注意. 2. [强制]创建线程或线程池时请指定有意义的线程名称,方便出错时回溯.正例: ...

  7. Android 性能优化(20)多核cpu入门:SMP Primer for Android

    SMP Primer for Android 1.In this document Theory Memory consistency models Processor consistency CPU ...

  8. 阿里巴巴 Java 开发手册 1.4.0

    一.编程规约(一) 命名风格1. [强制]代码中的命名均不能以下划线或美元符号开始,也不能以下划线或美元符号结束.反例: _name / __name / $name / name_ / name$ ...

  9. 阿里巴巴Java开发手册之并发处理注意事项

    1. [强制]获取单例对象需要保证线程安全,其中的方法也要保证线程安全.说明:资源驱动类.工具类.单例工厂类都需要注意.2. [强制]创建线程或线程池时请指定有意义的线程名称,方便出错时回溯.正例:p ...

随机推荐

  1. 论文笔记之:Human-level control through deep reinforcement learning

    Human-level control through deep reinforcement learning Nature 2015 Google DeepMind Abstract RL 理论 在 ...

  2. ios苹果机系统的1px显示解决方案

    1px边框在iPhone高清屏下,其实会变成2个物理像素的边框. /* 解决一像素问题 */ .navigation:before{ content: ""; pointer-ev ...

  3. sql server 存储过程 procedure

    https://www.cnblogs.com/selene/p/4483612.html

  4. POJ 1753 Flip Game(bfs+位压缩运算)

    http://poj.org/problem?id=1753 题意:一个4*4的棋盘,只有黑和白两种棋子,每次翻转一个棋子,并且其四周的棋子也跟着翻转,求棋盘全为黑或全为白时所需的最少翻转次数. 思路 ...

  5. [转]VS“当前上下文中不存在名称“ViewBag”解决方法

    原文链接:<VS"当前上下文中不存在名称"ViewBag",当前上下文不存在名称"model""-已解决> 自己的项目出现了错误 ...

  6. python 数据交换

    例1 def change(data): data[],data[]=data[],data[] print('函数内交换位置后:') ): print('data[%d]=%3d' %(i,data ...

  7. EsayUI + MVC + ADO.NET(仓储基础接口)

      1.RepositoryFramework(仓储接口:无外乎就是CRUD) 1.IAddRepository(添加接口) using System; namespace Notify.Infras ...

  8. 【转】总结C++中取成员函数地址的几种方法

    转自:“http://www.cnblogs.com/nbsofer/p/get_member_function_address_cpp.html” 这里, 我整理了4种C++中取成员函数地址的方法, ...

  9. Eclipse 打包jar

    1.将项目打成jar: 1.1 要将项目打包成jar文件,方法很多,可以用Eclipse自带的打包工具Ant打包,也可以用Eclipse的Export生成jar.经过尝试后,我不推荐用Ant打包,因为 ...

  10. windows下的mongodb安装(真垃圾)

    一.下载 3.6下载安装会卡死.只能下3.4的.http://downloads.mongodb.org/win32/mongodb-win32-x86_64-2008plus-ssl-v3.4-la ...