C05|C05 单例模式 懒汉式的线程安全问题及解决方案(二) 双重检查 & volatile
双重检查基础版
- 进入getInstance()做第一次检查;
- 进入synchronized代码块做第二次检查;
- 这样使用synchronized将大幅降低把synchronized加在方法上的性能开销;
public class LazyDoubleCheckSingleton {private static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
private LazyDoubleCheckSingleton() {}public static LazyDoubleCheckSingleton getInstance() {
if (lazyDoubleCheckSingleton == null) {
synchronized (LazyDoubleCheckSingleton.class) {
if (lazyDoubleCheckSingleton == null) {
lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
// 1.分配内存给这个对象
// 2.初始化对象
// 3.设置lazyDoubleCheckSingleton指向刚分配的内存
}
}
}
return lazyDoubleCheckSingleton;
}}
双重检查基础版中由重排序引发的问题
- 做第一次检查的时候,如果lazyDoubleCheckSingleton != null,并不代表lazyDoubleCheckSingleton一定已经初始化完成了,造成这种情形的原因是指令重排序;
- lazyDoubleCheckSingleton = new LazyDoubleCheckSingleton();
这句在底层经历了3个动作:
1.分配内存给这个对象;
2.初始化对象;
3.设置 lazyDoubleCheckSingleton 指向刚分配的内存;
这3个动作中,2和3的动作可能颠倒,其造成的结果就是:Thread-0第一次检查的时候,由于Thread-1先执行3,lazyDoubleCheckSingleton 指向刚分配的内存,导致Thread-0看到的 lazyDoubleCheckSingleton 不为空,直接返回 lazyDoubleCheckSingleton,但此时lazyDoubleCheckSingleton 在Thread-1中还没有初始化,所以造成程序出问题; - Java规范中有个 intra-thread semantics 的规定,它保证重排序不会改变单线程的执行结果,重排序可以提高程序的执行性能;
- 不允许重排序;
- 允许重排序,但不允许另一个线程看到这个重排序;
- 用 volatile 修饰 lazyDoubleCheckSingleton,就禁止了重排序;
- 在多线程的时候,多CPU会共享内存,加了 volatile 后,所有的线程都能看到共享内存的最新状态,保证了内存的可见性;
- 用 volatile 修饰的共享变量在进行写操作的时候,会将当前CPU缓存行的数据写进内存,使得其他CPU缓存了该内存地址的数据无效,从而迫使其他CPU重新从共享内存中获取数据,这样就保证了内存的可见性;
private volatile static LazyDoubleCheckSingleton lazyDoubleCheckSingleton = null;
推荐阅读
- 单例与静态类
- 多线程Debug窥探单例模式
- 云集品浅谈,社群商业模式的价值在哪()
- 掌握“性格学习法”,开启“学霸”模式
- 我们人生大管家——心智模式
- 设计模式|设计模式 - 代理模式Proxy
- --木木--|--木木-- 第二课作业#翼丰会(每日一淘6+1实战裂变被动引流# 6+1模式)
- 设计模式-代理模式-Proxy
- 【译】Rails|【译】Rails 5.0正式发布(Action Cable,API模式等)
- java静态代理模式