转自:
http://blog.csdn.net/zhangzeyuaaa/article/details/42673245
单例模式有如下实现方式:
public class Singleton { private static Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
这种方式称为延迟初始化,但是在多线程的情况下会失效。
于是使用同步锁,给getInstance() 方法加锁:
public class Singleton { private static Singleton instance; private Singleton() { } public static synchronized Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
但是同步是需要开销的,我们只需要在初始化的时候同步,而正常的代码执行路径不需要同步。
于是有了双重检查加锁(DCL):
public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { if (instance == null) { instance = new Singleton(); } } } return instance; }
这样一种设计可以保证只产生一个实例,并且只会在初始化的时候加同步锁,看似精妙绝伦,但却会引发另一个问题,这个问题由指令重排序引起。
指令重排序是为了优化指令,提高程序运行效率。
指令重排序包括编译器重排序和运行时重排序。
JVM规范规定,指令重排序可以在不影响单线程程序执行结果前提下进行。
例如 instance = new Singleton() 可分解为如下伪代码:
memory = allocate(); //1:分配对象的内存空间 ctorInstance(memory); //2:初始化对象 instance = memory; //3:设置instance指向刚分配的内存地址
但是经过重排序后如下:
emory = allocate(); //1:分配对象的内存空间 instance = memory; //3:设置instance指向刚分配的内存地址 // 注意,此时对象还未初始化 ctorInstance(memory); //2:初始化对象
将第2步和第3步调换顺序,在单线程情况下不会影响程序执行的结果,但是在多线程情况下就不一样了。线程A执行了instance = memory(这对另一个线程B来说是可见的),此时线程B执行外层 if (instance == null),发现instance不为空,随即返回,但是得到的却是未被完全初始化的实例,在使用的时候必定会有风险,这正是双重检查锁定的问题所在!
鉴于DCL的缺陷,便有了修订版(仍然有问题):
public static Singleton getInstance() { if (instance == null) { synchronized (Singleton.class) { Singleton temp = instance; if (temp == null) { synchronized (Singleton.class) { temp = new Singleton(); } instance = temp; } } } return instance; }
修订版试图引进局部变量和第二个synchronized来解决指令重排序的问题。
但是,Java语言规范虽然规定了同步代码块内的代码必须在对象锁释放之前执行完毕,却没有规定同步代码块之外的代码不能在对象锁释放之前执行,也就是说同步块里的代码必须在退出同步时完成,而同步块后面的代码则可以被编译器或运行时环境移到同步块中执行。
instance = temp 可能会在编译期或者运行期移到里层的synchronized内,于是又会引发跟DCL一样的问题。
在JDK1.5之后,可以使用volatile变量禁止指令重排序,让DCL生效:
public class Singleton { private static volatile Singleton instance; private Singleton() { } public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; } }
在JDK1.5及其后续版本中,扩充了volatile语义,系统将不允许对 写入一个volatile变量的操作与其之前的任何读写操作 重新排序,也不允许将 读取一个volatile变量的操作与其之后的任何读写操作 重新排序。
单例模式还有如下实现方式:
public class Singleton { private static class InstanceHolder { public static Singleton instance = new Singleton(); } private Singleton() { } public static Singleton getInstance() { return InstanceHolder.instance; } }
这种方式称为延迟初始化占位(Holder)类模式。该模式引进了一个静态内部类(占位类),在内部类中提前初始化实例,既保证了Singleton实例的延迟初始化,又保证了同步。这是一种提前初始化(恶汉式)和延迟初始化(懒汉式)的综合模式。
至此,正确的单例模式有三种实现方式:
1.提前初始化。
public class Singleton { private static Singleton instance = new Singleton(); private Singleton() { } public static Singleton getInstance() { return instance; } }
2.双重检查锁定 + volatile。
3.延迟初始化占位类模式。
相关推荐
《C++ and the Perils of Double Checked Locking》是一篇探讨C++编程中双重检查锁定(Double-Checked Locking)模式潜在问题的文献。在多线程编程中,双重检查锁定是一种常见的优化策略,旨在减少对同步原语的依赖...
在Java中,有多种实现单例模式的方法,包括简单实现、双重检查锁定(Double-Checked Locking)、静态内部类和枚举类。下面我们将详细探讨这些不同的实现方式。 1. **简单实现(非线程安全)** 最简单的单例实现...
DCL(Double-checked locking)是Java双重检查加锁单例模式的一种实现方法。它使用了synchronized关键字来确保线程安全,但是这也会带来性能损失。DCL看起来是一个聪明的优化,但是它却不能保证正常工作。 在多线程...
但是,如果不进行同步控制,懒汉式在多线程环境下可能会创建多个实例,因此通常采用双重检查锁定(Double-Checked Locking,DCL)来实现线程安全的懒汉式单例: ```java public class Singleton { private ...
懒汉式的常见实现是使用双重检查锁定(Double-Checked Locking,DCL): ```java public class Singleton { private volatile static Singleton instance; private Singleton() {} public static Singleton ...
另一个解决方案是使用双重检查锁定机制(Double-Checked Locking)来实现线程安全的单例模式。这种方法可以减少 synchronize 的使用,从而提高系统的性能。 ```java public class Singleton { private volatile ...
3. **双检单例(Double-Checked Locking)**: 双检锁模式试图在保证线程安全的同时减少不必要的同步开销。其核心思想是只有在实例为空时才进行同步操作: ```csharp public class Singleton { private static ...
为了解决这个问题,我们可以采用"双检锁/双重校验锁"(DCL,即Double-Checked Locking)的策略,这是一种优化过的懒汉式单例。DCL模式在Java中如下所示: ```java public class SingletonKerriganB { private ...
3. 双重检查锁定(Double-Checked Locking):结合了前两者,延迟初始化并保证线程安全。 ```java public class Singleton { private volatile static Singleton instance; private Singleton() {} public static...
1. **双检锁/双重校验锁(DCL,即 double-checked locking)** ```java public class Singleton { private volatile static Singleton instance; private Singleton() {} public static Singleton getInstance...
双重检查锁定(Double-Checked Locking, DCL)技术在懒汉式的同步基础上做了优化,只在第一次实例化时进行同步操作,避免了每次调用都同步带来的性能损耗。需要注意的是,这里使用了 `volatile` 关键字来确保可见性...
在多线程环境下,线程安全的懒汉模式通常采用**双重检查锁定(Double-Checked Locking,DCL)**策略。这种策略是在获取单例实例时进行两次检查:第一次检查是在无须同步的情况下完成的,如果发现单例未被创建,则...
// 双检锁/双重校验锁(DCL,即 double-checked locking) public class Singleton { private volatile static Singleton instance; private Singleton() {} public static Singleton getInstance() { if ...
3. 双重检查锁定(Double-Checked Locking):结合了前两者的优势,既延迟初始化又保证线程安全。这种方式在Java 5及以上版本中是安全的,因为它依赖于 volatile 关键字来确保可见性和有序性。 ```java public ...
为了保证线程安全,可以使用双重检查锁定模式(Double-Checked Locking Pattern),确保只有一个实例被创建。但是这要求必须使用关键字volatile修饰静态实例变量,以防止指令重排序导致的问题。单例模式1和单例模式2...
2. **双检锁/双重校验锁定(Double-Checked Locking,DCL)** 这种方法在早期的.NET框架中被广泛使用,但后来由于.NET内存模型的变化,其线程安全性变得复杂。在.NET 4.0及更高版本中,可以安全地使用此方法: ``...
#### 五、双重检查锁定(Double-Checked Locking)单例模式 为了解决懒汉式单例模式在多线程环境下的性能问题,可以采用双重检查锁定策略。这种方法只在必要时进行同步,提高了效率。 ```java public class ...
3. 双重检查锁定(Double-Checked Locking) 双重检查锁定是一种优化懒汉式单例模式的写法,它可以在多线程环境中保证实例的唯一性,同时避免不必要的同步开销。通过在声明实例变量时加上volatile关键字,可以确保在...