转自:
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.延迟初始化占位类模式。
相关推荐
这种写法可以保证线程安全.两个if都是不能去掉的.如果去掉第一个if: 那么所有的线程都会到这里来先获取锁,然后判断singleton是否为空.所有线程都会串行
目录 1.场景: 2.对象的创建过程 3.指令重排 4.CPU执行时间片 5.指令重排对双重检查加锁模式的影响 1.场景: ...面试官:为什么双重检查加锁需要加volatile关键字? 我:要不我们问问度娘?
懒汉式优化-加锁同步3.DCL双检锁/双重校验锁重排序问题多线程执行时序表volatile 作用优化-基于volatile 的双重检查锁4.IODH按需初始化持有者反射问题私有构造函数异常处理5.枚举实现单例使用推荐 什么是单例? 单例...
通常单例模式在Java语言中,有两种构建方式: 懒汉式—线程不安全:最基础的实现方式,线程上下文单例,不需要共享给所有线程,也不需要加synchronize之类的锁,以提高性能。 懒汉式—线程安全:加上synchronize之类...
The electronics and information technology revolution continues, but it is a critical time in the development of technology. Once again, we stand on the brink of a new era where emerging research will...
JEDEC JESD251A:2020 EXpanded Serial Peripheral Interface (xSPI) for Non Volatile Memory Devices, Version 1.0 - 完整英文电子版(75页).pdf
Java并发编程系列- volatile;Java并发编程系列- volatile;Java并发编程系列- volatile;Java并发编程系列- volatile;Java并发编程系列- volatile;
饿汉式 线程安全,调用效率高,但是不能延时加载。 public class Singleton { private static Singleton instance = new ...如果方法没有 synchronized,单例没有 volatile , 则线程不安全。 public class Singleton {
double - 6 - else - 6 - extends - 6 - false - 7 - final - 7 - finally - 7 - float - 8 - for - 8 - if - 8 - implements - 9 - import - 9 - instanceof - 9 - int - 9 - interface - 10 - long - 10 - native ...
单例模式可能是代码最少的模式了,但是少不一定意味着简单,想要用好、用对单例模式,还真得费一番脑筋。本文对Java中常见的单例模式写法做了一个总结,如有错漏之处,恳请读者指正。 饿汉法 顾名思义,饿汉法就是在...
volatile and nonvolatile. Static and dynamic random-access memories (SRAM and DRAM) are examples of volatile memories that can be accessed in nanosecond of speed, but the stored data will be lost when...
GMW 15634-2020 Determination of Volatile and Semi-Volatile Organic Compounds.pdf
有的并发处理都有排队等候,唤醒,执行至少三个这样的步骤.所以并发肯定是宏观概念,在微观上他们都是序列被处理的,只不过资源不会在某一个上被阻塞(一般是通过时间片轮转),所以在宏观上看多个几乎同时到达的请求...
GMW 15654-2020 Determination of Volatile and Semi-Volatile Organic Compounds.pdf
GMW 15654-2013 Determination of Volatile and Semi-Volatile Organic Compounds.pdf
单例模式最初的定义出现于《设计模式》(艾迪生维斯理, 1994):“保证一个类仅有一个实例,并提供一个访问它的全局访问点。” 而我对单例的理解是,在可控的范围内充当全局变量的作用,就相当于C语言中一个全局...
处理器重排序与内存屏障指令 happens-before 重排序 数据依赖性 as-if-serial 语义 程序顺序规则 重排序对多线程的影响 顺序一致性 数据竞争与顺序一致性保证 顺序一致性内存模型 同步程序的顺序一致性效果 未同步...
详细说明 并举例说明了VOlatile的作用及用法,特别是嵌入式程序员要注意的
volatile与synchronized的区别,锁提供了两种主要特性:互斥(mutual exclusion) 和可见性(visibility)
3.8 volatile变量检查 4 PC-Lint软件使用方法 4.1 安装与配置 4.2 PC-Lint与常用开发工具的集成(Visual C++,Source Insight,UEdit) 5 总结 参考文献 附录一 PC-Lint 重要文件说明 附录二 错误信息禁止选项说明 ...