在一些人的印象中,懒汉式单例可能是这样的:
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机制来避免多线程竞争资源的问题,并且懒汉...
深入分析java单例模式什么是单例模式单例模式的常见写法一、饿汉式单例优点缺点示例二、懒汉式单例示例1(普通写法)示例2(synchronized写法)示例3(DCL写法)示例4(内部类写法)三、注册式单例示例1(容器式)示例2(枚举式...
Singleton模式可以相当的复杂,比头五种模式加起来还复杂,譬如涉及到DCL双锁检测(double checked locking)的讨论、涉及到多个类加载器(ClassLoader)协同时、涉及到跨JVM(集群、远程EJB等)时、涉及到单例对象...
一个单例模式的晋级过程(饿汉-懒汉-DCL-IoDH-枚举) 文章目录一个单例模式的晋级过程(饿汉-懒汉-DCL-IoDH-枚举)什么是单例?单例有哪些运用场景?实现1.饿汉式测试优化-final2.懒汉式优化-加锁同步3.DCL双检锁/双重...
懒汉式单例模式的实现方式有三种: * 双重检查锁方式(DCL) 12345678910111213 单例模式: 懒汉式:延迟加载方式 饿汉式:立即加载 面试绝对面的都是懒汉式的问题。 单例模式如果使用不当,就容易引起线程安全问题...
单例模式 一个类只有一个实例,并且可以全局访问使用 应用场景 如账户管理类,数据库操作类等(某个对象频繁被访问使用) 常用方式 饿汉式 懒汉式 同步加锁 DCL双重加锁验证 静态内部类 枚举单例 饿汉式 加载类的...
这个小程序涉及到了以下知识点: Java基础知识 队列《数据结构》 单例模式“双检锁/双重校验锁(DCL,即 double-checked locking)”
设计模式之 Singleton(单态/单件) 阎宏博士讲解:单例(Singleton)模式 保证一个类只有一个实例,并提供一个访问它的全局访问点 设计模式之 Factory(工厂方法和抽象工厂) 使用工厂模式就象使用 new 一样频繁. ...