双重检查锁定失败可能性
创始人
2024-07-26 08:10:30
0

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

 

  1. public class Singleton {  
  2.  
  3.     private static Singleton instance = null;  
  4.  
  5.     private Singleton(){}  
  6.  
  7.       
  8.  
  9.     public static Singleton  getInstance() {  
  10.  
  11.        if(instance == null) {  
  12.  
  13.            instance = new Singleton();  
  14.  
  15.        }  
  16.  
  17.        return instance;  
  18.  
  19.     }  
  20.  
  21. }  

 

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

 

  1. public class Singleton {  
  2.  
  3.     private static Singleton instance = null;  
  4.  
  5.     private Singleton(){}  
  6.  
  7.       
  8.  
  9.     public synchronized static Singleton getInstance() {  
  10.  
  11.        if(instance == null) {  
  12.  
  13.            instance = new Singleton();  
  14.  
  15.        }  
  16.  
  17.        return instance;  
  18.  
  19.     }  
  20.  
  21. }  

 

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

 

  1. public class Singleton {  
  2.  
  3.     private static Singleton instance = null;  
  4.  
  5.     private Singleton(){}  
  6.  
  7.       
  8.  
  9.     public static Singleton getInstance() {  
  10.  
  11.        if(instance == null) {  
  12.  
  13.            synchronized(Singleton.class) {  
  14.  
  15.               if(instance == null) {  
  16.  
  17.                   instance = new Singleton();  
  18.  
  19.               }  
  20.  
  21.            }  
  22.  
  23.        }  
  24.  
  25.        return instance;  
  26.  
  27.     }  
  28.  
  29. }  

 

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

原因在于:初始化Singleton 和 将对象地址写到instance字段 的顺序是不确定的。在某个线程new Singleton()时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化;此时若另外一个线程来调用getInstance,取到的就是状态不正确的对象。

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

 

  1. public class Singleton {  
  2.  
  3.     private static Singleton instance = null;  
  4.  
  5.     private Singleton(){}  
  6.  
  7.       
  8.  
  9.     public static Singleton getInstance() {  
  10.  
  11.        if(instance == null) {  
  12.  
  13.            Singleton temp;  
  14.  
  15.            synchronized(Singleton.class) {  
  16.  
  17.               temp = instance;  
  18.  
  19.               if(temp == null) {  
  20.  
  21.                   synchronized(Singleton.class) {  
  22.  
  23.                      temp = new Singleton();  
  24.  
  25.                   }  
  26.  
  27.                   instance = temp;  
  28.  
  29.               }  
  30.  
  31.            }  
  32.  
  33.        }  
  34.  
  35.        return instance;  
  36.  
  37.     }  
  38.  
  39. }  

 

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

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

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

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

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

 

  1. public class Singleton {  
  2.  
  3.     private static volatile Singleton instance = null;  
  4.  
  5.     private Singleton(){}  
  6.  
  7.       
  8.  
  9.     public static Singleton getInstance() {  
  10.  
  11.        if(instance == null) {  
  12.  
  13.            synchronized(Singleton.class) {  
  14.  
  15.               if(instance == null) {  
  16.  
  17.                   instance = new Singleton();  
  18.  
  19.               }  
  20.  
  21.            }  
  22.  
  23.        }  
  24.  
  25.        return instance;  
  26.  
  27.     }  
  28.  
  29. }  

 

需要注意的是:在JDK1.4以及之前的版本中,该方式仍然有问题。

【编辑推荐】

  1. 有趣的Java对象序列化缓存问题
  2. 关于Java对象序列化您不知道的5件事
  3. Java 7 I/O新功能探秘:同步操作,多播与随机存取
  4. Java实用技巧:当不能抛出checked异常时
  5. 多线程开发的捷径:构建Java并发模型框架

相关内容

热门资讯

如何允许远程连接到MySQL数... [[277004]]【51CTO.com快译】默认情况下,MySQL服务器仅侦听来自localhos...
如何利用交换机和端口设置来管理... 在网络管理中,总是有些人让管理员头疼。下面我们就将介绍一下一个网管员利用交换机以及端口设置等来进行D...
施耐德电气数据中心整体解决方案... 近日,全球能效管理专家施耐德电气正式启动大型体验活动“能效中国行——2012卡车巡展”,作为该活动的...
20个非常棒的扁平设计免费资源 Apple设备的平面图标PSD免费平板UI 平板UI套件24平图标Freen平板UI套件PSD径向平...
德国电信门户网站可实时显示全球... 德国电信周三推出一个门户网站,直观地实时提供其安装在全球各地的传感器网络检测到的网络攻击状况。该网站...
为啥国人偏爱 Mybatis,... 关于 SQL 和 ORM 的争论,永远都不会终止,我也一直在思考这个问题。昨天又跟群里的小伙伴进行...
《非诚勿扰》红人闫凤娇被曝厕所... 【51CTO.com 综合消息360安全专家提醒说,“闫凤娇”、“非诚勿扰”已经被黑客盯上成为了“木...
2012年第四季度互联网状况报... [[71653]]  北京时间4月25日消息,据国外媒体报道,全球知名的云平台公司Akamai Te...