`
freish
  • 浏览: 83961 次
  • 性别: Icon_minigender_1
  • 来自: 摄影帝国
社区版块
存档分类
最新评论

双重检查锁定失败可能性——参照《The "Double-Checked Locking is Broken" Declaration》

    博客分类:
  • java
阅读更多

 

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

 

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;

    }

}

 

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

 

16
8
分享到:
评论
20 楼 zhangdong92 2017-02-08  
我有一个想法,增加一个字段boolean initFinish=false;
每次读取instance时不再判断instance==null,而是判断initFinish是否为true。在初始化时,
instance=new Instance();
initFinish=true;

这样是否可以解决?
19 楼 qq32933432 2017-01-20  
将instance = temp移动到最里层的同步块内,这样就出现了上个版本同样的问题。

请问下这里是什么意思,上个版本是因为对象还没有构造完成另一个线程读取了这个对象,所以导致对象不完整,但是这里我觉得应该没问题吧?在代码走到instance = temp这一段的时候前面new Singleton();肯定已经执行完毕了呀  对象已经成功构建了,并不会造成对象不完整吧?
18 楼 liuInsect 2014-03-12  
"原因在于:初始化Singleton  和 将对象地址写到instance字段 的顺序是不确定的。在某个线程new Singleton()时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化;此时若另外一个线程来调用getInstance,取到的就是状态不正确的对象。"

看不懂。
17 楼 happyJavaer 2012-12-08  
freish 写道
引用
此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化;

我的意思是说:在已经初始化完成了,调用了构造方法了,不会还没有初始化吧



这里的初始化显然同“装载连接初始化”中的初始化含义不一样

假设有以下语句:
String str = new String();

是分成下面几条指令执行的:
0:   new     #2; //class java/lang/String
1:   dup
2:   invokespecial   #3; //Method java/lang/String."<init>":()V
3:   astore_1

到第2步才是调用构造方法,第三步是给引用赋值,但第2步与第3步的顺序能否保证呢,有没有可能被reorder呢


你好,请问一下
synchronized(Singleton.class) {

              if(instance == null) {

                  instance = new Singleton();

              }
}

          
这段代码不是说明了若线程A取得锁,执行实例化的操作的话,线程B必须要等到线程A执行完这段代码后并释放锁后才能进入同步块吧?是不是说线程A中只取得了把单例对象的地址赋值给instance而没有执行对象的init方法就把锁释放了让线程B可以取得锁?
16 楼 freish 2012-09-25  
原文以及原话:http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

JDK5 and later extends the semantics for volatile so that the system will not allow a write of a volatile to be reordered with respect to any previous read or write, and a read of a volatile cannot be reordered with respect to any following read or write.


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

错。
JDK1.5及其后续版本中,多个 volatile field 间当然还是不能 reorder(和JDK1.4一样)。但,volatile field和non-volatile field间的reorder却没那么容易了(也就是依然可以reorder)。
举例:线程B读取 线程A写入的volatile v时,线程B可以看到 线程A在写入v之前的所有操作(对于线程A来讲, 其“写入v 这个操作”以及“写入v之前的 所有操作”依然还是可以reorder的)

http://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html#volatile
引用
In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.
15 楼 iceman1952 2012-09-25  
引用
在JDK1.5及其后续版本中,扩充了volatile语义,系统将不允许对 写入一个volatile变量的操作与其之前的任何读写操作 重新排序,也不允许将 读取一个volatile变量的操作与其之后的任何读写操作 重新排序。

错。
JDK1.5及其后续版本中,多个 volatile field 间当然还是不能 reorder(和JDK1.4一样)。但,volatile field和non-volatile field间的reorder却没那么容易了(也就是依然可以reorder)。
举例:线程B读取 线程A写入的volatile v时,线程B可以看到 线程A在写入v之前的所有操作(对于线程A来讲, 其“写入v 这个操作”以及“写入v之前的 所有操作”依然还是可以reorder的)

http://www.cs.umd.edu/users/pugh/java/memoryModel/jsr-133-faq.html#volatile
引用
In effect, because the new memory model places stricter constraints on reordering of volatile field accesses with other field accesses, volatile or not, anything that was visible to thread A when it writes to volatile field f becomes visible to thread B when it reads f.
14 楼 freish 2012-03-19  
引用
此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化;

我的意思是说:在已经初始化完成了,调用了构造方法了,不会还没有初始化吧



这里的初始化显然同“装载连接初始化”中的初始化含义不一样

假设有以下语句:
String str = new String();

是分成下面几条指令执行的:
0:   new     #2; //class java/lang/String
1:   dup
2:   invokespecial   #3; //Method java/lang/String."<init>":()V
3:   astore_1

到第2步才是调用构造方法,第三步是给引用赋值,但第2步与第3步的顺序能否保证呢,有没有可能被reorder呢
13 楼 glmylove 2012-03-18  
freish 写道
glmylove 写道
引用
原因在于:初始化Singleton  和 将对象地址写到instance字段 的顺序是不确定的。在某个线程new Singleton()时,在构造方法被调用之前,就为该对象分配了内存空间并将对象的字段设置为默认值。此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化;此时若另外一个线程来调用getInstance,取到的就是状态不正确的对象。


这段话不太认同,JAVA的类加载、连接、初始化这个过程是线程安全的,在初始化之前,是不可能使用这个类的吧?



所谓的线程安全不就是保证不会有第二个线程对该类进行装载连接初始化么?

而在new对象的时候,装载-连接-初始化<clinit>必然已经完成了,换而言之,new的时候与装载-连接-初始化已经木有关系了

new的时候,只涉及内存空间的分配赋默认值以及<init>的调用了

在赋默认值之后,调用<init>之前,个人认为是有可能和后面的指令reorder的吧


引用
此时就可以将分配的内存地址赋值给instance字段了,然而该对象可能还没有初始化;


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


这段话不太认同,JAVA的类加载、连接、初始化这个过程是线程安全的,在初始化之前,是不可能使用这个类的吧?



所谓的线程安全不就是保证不会有第二个线程对该类进行装载连接初始化么?

而在new对象的时候,装载-连接-初始化<clinit>必然已经完成了,换而言之,new的时候与装载-连接-初始化已经木有关系了

new的时候,只涉及内存空间的分配赋默认值以及<init>的调用了

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


这段话不太认同,JAVA的类加载、连接、初始化这个过程是线程安全的,在初始化之前,是不可能使用这个类的吧?
10 楼 dddd8579 2011-10-11  
mark下
9 楼 adzshai 2011-09-20  
不幸的是java程序员,
写个恶汉单例也那么多规则
8 楼 Reset 2011-07-18  
volatile 关键字修饰类成员变量
7 楼 csuyux 2011-06-15  
star022 写道
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

恩,饿汉式单例更符合JAVA语言特点。
6 楼 star022 2011-06-07  
public class Singleton {
    private static final Singleton INSTANCE = new Singleton();

    private Singleton() {
    }

    public static Singleton getInstance() {
        return INSTANCE;
    }
}
5 楼 freish 2011-04-20  
xbclll 写道
很好的想法!不幸的是,该方案也未能解决问题之根本:



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



求教楼主:以上这段话我不懂什么意思,难道instance = new Singleton();不是先new出这个对象,然后再付给instance这个句柄嘛?

因为new在jvm层次是由多个指令完成的,所以允许在 给对象分配内存之后,未调用构造方法之前给引用赋值
4 楼 xbclll 2011-04-20  
很好的想法!不幸的是,该方案也未能解决问题之根本:



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



求教楼主:以上这段话我不懂什么意思,难道instance = new Singleton();不是先new出这个对象,然后再付给instance这个句柄嘛?
3 楼 renwolang521 2011-04-20  
还有一种内部类的写法
2 楼 freish 2011-04-19  
woaiwofengkuang 写道
1.5以上用enum很简单的


主要不是为讨论单例,只是为了简单说说java内存模型
1 楼 woaiwofengkuang 2011-04-19  
1.5以上用enum很简单的

相关推荐

    C++ and the Perils of Double-Checked Locking

    在介绍双检锁模式(Double-Checked Locking Pattern,DCLP)的C++实现中,Scott Meyers和Andrei Alexandrescu在其2004年的文章中指出,传统的单例模式实现并不具备线程安全性。单例模式是设计模式中经常被提及的一种...

    C++ and the Perils of Double Checked Locking.zip

    《C++ and the Perils of Double Checked Locking》是一篇探讨C++编程中双重检查锁定(Double-Checked Locking)模式潜在问题的文献。在多线程编程中,双重检查锁定是一种常见的优化策略,旨在减少对同步原语的依赖...

    c++ and Peris of Double Checked Locking

    标题:C++与双检查锁定(Double Checked Locking)的陷阱 描述:C++如何解决单例模式的线程安全问题 ### 关键知识点解析: #### 单例模式的线程安全挑战 单例模式是一种设计模式,确保一个类只有一个实例,并提供...

    java面试——深圳-乐信-Java高级.zip

    在Java高级面试中,面试官通常会关注候选人在核心Java、多线程、集合框架、JVM内存管理、设计模式、数据库操作、...以上内容是Java高级面试中可能涉及的关键知识点,深入理解和掌握这些知识将有助于在面试中表现出色。

    Qt5学习笔记——QRadioButton与QButtonGroup - R先生一天不学习就浑身难受 - CSDN博客1

    Qt5学习笔记——QRadioButton与QButtonGroup Qt5学习笔记——QRadioButton与QButtonGroup是Qt5中两个重要的控件,用于实现单选按钮的功能。本文将详细介绍QRadioButton和QButtonGroup的使用方法和属性。 一、...

    深究AngularJS——ng-checked(回写:带真实案例代码)

    本文深入探讨了AngularJS框架中的ng-checked指令,通过真实案例代码的形式展示了如何使用ng-checked实现复选框的状态记忆与回写。ng-checked指令在AngularJS中用于获取和设置复选框(checkbox)的选中状态。它依赖于...

    AngularJS入门教程之ng-checked 指令详解

    在Web开发中,AngularJS作为前端框架被广泛使用,它提供了一套完整的...虽然在不同的场景下可能需要配合其他AngularJS指令和数据绑定来共同实现复杂的功能,ng-checked仍然作为基础指令被频繁应用于各种动态交互中。

    深入剖析Java中的双检锁模式:实现、陷阱与最佳实践

    在Java并发编程中,双检锁(Double-Checked Locking)是一种用于减少同步开销的优化技术,尤其适用于懒加载(lazy initialization)的场景。本文将详细探讨双检锁的工作原理、潜在问题以及如何安全地实现它。 双检锁...

    双重检查锁

    双重检查锁(Double-Checked Locking, DCL)是一种在多线程环境中用于实现懒加载(lazy loading)的设计模式。它通过两次检查来确定是否需要获取锁,从而避免不必要的同步操作,提高程序性能。然而,DCL的实现并不像表面...

    element-ui tree树形控件:default-checked-keys 设置空数组 默认还是选中状态问题

    在这个问题中,开发者遇到了一个关于`default-checked-keys`属性的问题,即当尝试将该属性设置为空数组时,树形控件的节点依然保持选中的状态,这与预期的行为不符。 `default-checked-keys`属性是Element UI Tree...

    Java双重检查加锁单例模式的详解

    DCL(Double-checked locking)是Java双重检查加锁单例模式的一种实现方法。它使用了synchronized关键字来确保线程安全,但是这也会带来性能损失。DCL看起来是一个聪明的优化,但是它却不能保证正常工作。 在多线程...

    Java-设计模式-单例模式-实现源码(简单实现、双重检查锁、静态内部类、枚举类)

    在Java中,有多种实现单例模式的方法,包括简单实现、双重检查锁定(Double-Checked Locking)、静态内部类和枚举类。下面我们将详细探讨这些不同的实现方式。 1. **简单实现(非线程安全)** 最简单的单例实现...

    Java多线程之延迟初始化1

    为了解决性能问题,引入了"双重检查锁定"(Double-Checked Locking)策略: ```java public class DoubleCheckedLocking { private volatile static Instance instance; public static Instance getInstance() { ...

    毕向东静态07

    视频可能详细讲述了如何在Java中实现单例的第二种方式,这通常涉及到懒汉式(Lazy Initialization)的双重检查锁定(Double-Checked Locking)。 在Java中,单例模式的实现通常有两种主要方法: 1. 饿汉式(Eager ...

    Software Adaptation in an Open Environment: A Software Architecture Perspective

    Software Adaptation in an Open Environment: A Software Architecture Perspective by Yu Zhou ...The organization and presentation of the book will be double-checked by professional scholars

    Javascript操作表单实例讲解(下)

    在上篇文章给大家介绍了js操作表单实例讲解(下)的相关...———————————————- checked 返回或设置单选的选中状态 true 选中 false 未选中 value 属性 获取选中的值,必须先判断选中状态 ———————

    ACE中的DoubleCheckedLocking模式

    北京火龙果软件工程技术中心意图无论什么时候当临界区中的代码仅仅需要加锁一次,同时当其获取锁的时候必须是线程安全的,可以用DoubleCheckedLocking模式来减少竞争和加锁载荷。动机1、标准的单例。开发正确的有效...

    java面试——深圳-丰巢科技-Java高级.zip

    - 异常处理:理解Checked异常和Unchecked异常的区别,以及如何有效地使用try-catch-finally语句。 - 内存管理:了解堆内存和栈内存的区别,以及对象的生命周期。 2. **并发处理**: - 线程:创建线程的多种方式...

    checked.css-超酷CSS3复选框和单选按钮点击动画库

    这里,`checked--effect-name`需要替换为23种动画效果中的一个,如`checked--pulse`或`checked--rotate`。 3. **自定义样式**:除了预设的动画效果,你还可以通过CSS对复选框和单选按钮的样式进行个性化调整,以...

Global site tag (gtag.js) - Google Analytics