`

单例模式与DCL

    博客分类:
  • java
 
阅读更多
在一些人的印象中,懒汉式单例可能是这样的:



public class Singleton {



    private static Singleton instance = null;

    private Singleton(){}




    public synchronized static Singleton getInstance() {



       if(instance == null) {

           instance = new Singleton();

       }



       return instance;

    }



}



这种写法,是安全的,不会产生多个实例。但是这种写法有个问题就是把synchronized关键字加在方法签名上,导致多线程访问的时候,锁是加在Singleton.class这个对象上的。每次调用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;



    }

}



这种写法,看上去很美,甚至被当作懒汉式单例的正确版本广为流传。不幸的是,这个版本也是错误的。这和DCL有关。虽然我也听说过java中双重检查成例是失效的,但是没有弄得很清楚到底它是怎么失效的,依稀记得跟编译器优化代码有关系。今天看了一些资料,终于明白了大概:

上述代码,并不会产生两个Singleton实例,它的错误表现在虽然一个线程得到了Singleton的实例,但是里面的状态是不对的。如果要弄明白具体的意思,来看一下下面的例子:



# public class LazySingleton {

#     private int someField;

#     

#     private static LazySingleton instance;

#     

#     private LazySingleton() {

#         this.someField = new Random().nextInt(200)+1;         // (1)

#     }

#     

#     public static LazySingleton getInstance() {

#         if (instance == null) {                               // (2)

#             synchronized(LazySingleton.class) {               // (3)

#                 if (instance == null) {                       // (4)

#                     instance = new LazySingleton();           // (5)

#                 }

#             }

#         }

#         return instance;                                      // (6)

#     }

#     

#     public int getSomeField() {

#         return this.someField;                                // (7)

#     }

# }

这也是同样错误的懒汉式单例,和上面不同之处是多了一个someField,以及相应的赋值和取值代码。这个单例,在某些极端的情况下会发生这样的现象:线程X获得了单例,但是调用getSomeField()的时候,取到的值并不是一个大于1的随机数,而是0.这种情况仅仅可能发生在初次调用的时候,而且是多个线程几乎同时调用的时候。由于构造方法的调用在字节码中并不是一个指令,而是4到5个指令。比如最简单的new一个String,字节码如下:

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



这样就会出现一种情况,实例已经创建了,但是构造方法还没被调用(<init>没被调用)。导致这个实例中的field都是默认的。



解决这个问题的办法是使用一个静态内部类来保存实例。如下面所示:



public class Something {

      

    private Something() {

        }



    private static class LazyHolder {

        public static final Something INSTANCE = new Something();

    }



    public static Something getInstance() {

        return LazyHolder.INSTANCE;



    }

}





或者,在JDK1.5之后,还可以采用另外一种办法,使用volatile关键字:



在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;

    }

}






看似小小的单例问题,竟然可以牵涉那么多的深层次问题。值得令人深思。





主题:用happen-before规则重新审视DCL http://www.iteye.com/topic/260515

双重检查锁定失败可能性——参照《The "Double-Checked Locking is Broken" Declaration》 http://freish.iteye.com/blog/1008304#bc2248177

分享到:
评论

相关推荐

    单例模式(饿汉模式、懒汉模式、DCL单例模式、枚举)

    饿汉模式、懒汉模式、DCL单例模式、枚举;不同情况下使用不同的单例创建模式

    设计模式经典案例之单例模式

    单例模式主要有懒汉式和饿汉式两种实现,饿汉式不会有线程安全的问题,但是提前构造对象占用了一定的资源,如果对内存要求较低的场景可以使用饿汉式实现;懒汉式应使用DCL机制来避免多线程竞争资源的问题,并且懒汉...

    java单例模式看这一篇就够了

    深入分析java单例模式什么是单例模式单例模式的常见写法一、饿汉式单例优点缺点示例二、懒汉式单例示例1(普通写法)示例2(synchronized写法)示例3(DCL写法)示例4(内部类写法)三、注册式单例示例1(容器式)示例2(枚举式...

    单例模式详解

    Singleton模式可以相当的复杂,比头五种模式加起来还复杂,譬如涉及到DCL双锁检测(double checked locking)的讨论、涉及到多个类加载器(ClassLoader)协同时、涉及到跨JVM(集群、远程EJB等)时、涉及到单例对象...

    一个单例模式的晋级过程(饿汉-懒汉-DCL-IoDH-枚举)

    一个单例模式的晋级过程(饿汉-懒汉-DCL-IoDH-枚举) 文章目录一个单例模式的晋级过程(饿汉-懒汉-DCL-IoDH-枚举)什么是单例?单例有哪些运用场景?实现1.饿汉式测试优化-final2.懒汉式优化-加锁同步3.DCL双检锁/双重...

    设计模式第二天学习内容

    懒汉式单例模式的实现方式有三种: * 双重检查锁方式(DCL) 12345678910111213 单例模式: 懒汉式:延迟加载方式 饿汉式:立即加载 面试绝对面的都是懒汉式的问题。 单例模式如果使用不当,就容易引起线程安全问题...

    Android设计模式之单例模式详解

    单例模式 一个类只有一个实例,并且可以全局访问使用 应用场景 如账户管理类,数据库操作类等(某个对象频繁被访问使用) 常用方式 饿汉式 懒汉式 同步加锁 DCL双重加锁验证 静态内部类 枚举单例 饿汉式 加载类的...

    基于Java零基础实现《学生成绩管理系统》的简单小项目.zip

    这个小程序涉及到了以下知识点: Java基础知识 队列《数据结构》 单例模式“双检锁/双重校验锁(DCL,即 double-checked locking)”

    二十三种设计模式【PDF版】

    设计模式之 Singleton(单态/单件) 阎宏博士讲解:单例(Singleton)模式 保证一个类只有一个实例,并提供一个访问它的全局访问点 设计模式之 Factory(工厂方法和抽象工厂) 使用工厂模式就象使用 new 一样频繁. ...

Global site tag (gtag.js) - Google Analytics