在一些人的印象中,懒汉式单例可能是这样的:
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)单例模式以及枚举单例。 1. **饿汉模式**: 饿汉模式是在类加载时就完成了实例化,避免了线程同步问题。这种方式简单且安全,但...
将单例模式与工厂模式结合,可以创建一个单例工厂,这个工厂类负责生成单例对象。这样做有两个主要好处:一是隐藏了单例的实现细节,使得代码更加整洁,降低了耦合度;二是可以通过工厂方法扩展新的实现,如果将来...
单例模式是软件设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。在C#中,单例模式常用于管理共享资源或控制类的实例化过程,以提高性能、节约系统资源,特别是在整个应用程序生命周期内只需要一...
单例模式是一种设计模式,它的主要目标是确保一个类只有一个实例,并提供一个全局访问点。在软件工程中,单例模式常用于控制资源的共享,比如数据库连接池、线程池或者日志系统等,这些资源通常需要全局唯一且高效地...
**设计模式——单例模式** 在软件工程中,设计模式是一种在特定场景下解决常见问题的标准方案,可以被复用并提升代码质量。单例模式是设计模式中的一种,它保证一个类只有一个实例,并提供一个全局访问点。这种模式...
单例模式是软件设计模式中的一种经典模式,其主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下非常有用,比如控制共享资源、管理配置对象等。下面将详细介绍七种常见的单例模式实现...
在Java中,实现单例模式有多种方法,包括懒汉式(线程不安全)、饿汉式(静态常量)、双检锁(DCL)和枚举单例。其中,双检锁和枚举单例是线程安全的,推荐在多线程环境下使用。 ```java // 双检锁/双重校验锁(DCL...
单例模式是软件设计模式中的一种,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。在Java或类似的面向对象编程语言中,单例模式常用于管理共享资源,如数据库连接池、线程池或者配置文件等。在这个...
单例模式是软件设计模式中的一种,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在很多场景下都非常有用,比如控制资源的唯一性、管理共享配置或者创建昂贵的对象时避免频繁创建销毁。 ...
Java中的单例模式是一种常用的软件设计模式,它保证一个类只有一个实例,并提供全局访问点。在Java编程中,单例模式常用于控制资源的访问,比如数据库连接池、线程池或者日志对象等。本篇文章将深入探讨如何在Java中...
3. **双重检查锁定(DCL,Double Check Locking)单例模式**:结合了饿汉模式和懒汉模式的优点,既延迟了初始化,又保证了线程安全。在多线程环境下,只有在`instance`为`null`时才会进入同步块,避免了不必要的同步...
下面是一个简单的DCL实现的懒汉式单例模式代码示例: ```java public class Singleton { private volatile static Singleton instance; // 使用volatile关键字防止指令重排序 private Singleton() {} public ...
单例模式有多种实现方式,常见的包括懒汉式、饿汉式以及双重检查锁定(DCL)等。 1. 懒汉式:懒汉式是在第一次使用时才创建单例对象,以延迟加载提高效率。但是,如果在多线程环境下,没有正确处理同步问题,可能...
### 三、单例模式的应用场景与优缺点 **应用场景**: - 全局日志对象 - 数据库连接池 - 缓存服务 - 线程池 - 多线程环境下的线程同步控制 **优点**: - 节省内存空间,减少系统资源消耗。 - 避免对资源的多重占用...
单例模式是软件设计模式中的一种基础且广泛应用的模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。这种模式在系统中需要频繁创建和销毁对象,且对象创建成本较高,或者需要共享资源的情况下非常...
#### 五、双重检查锁(DCL)单例模式 双重检查锁单例模式(Doubly Checked Locking Singleton)是懒汉式单例模式的一种改进版,既实现了延迟加载,又解决了多线程安全问题,同时也减少了同步的开销。具体实现如下: ...
单例模式是软件设计模式中的一种经典模式,它在许多场景下被广泛使用,尤其是在需要全局唯一实例的情况下。本文将深入探讨单例模式的概念、作用、实现方式以及其在实际编程中的应用。 单例模式的核心思想是确保一个...
单例模式是软件设计模式中的一种基础且广泛应用的模式,它的主要目的是确保一个类只有一个实例,并提供一个全局访问点。在Java中,实现单例模式有多种方法,每种都有其特定的优缺点和适用场景。以下是几种常见的单例...
策略模式和单例模式是软件设计中两种非常重要的设计模式,它们在实际开发中有着广泛的应用。在这篇文章中,我们将深入探讨这两种模式的核心概念、实现方式以及如何在实际项目中运用。 策略模式是一种行为设计模式,...
Java单例模式是一种设计模式,它允许在程序中创建唯一一个类实例,通常用于管理共享资源,例如数据库连接、线程池或者配置对象等。单例模式的核心在于限制类的构造函数,确保类只能被初始化一次,从而实现全局唯一的...