双重检查(Double-Check)
有一个优化的思路,就是把100%出现的防护盾,也改为1%的几率出现,使之只出现在可能会导致多个实例出现的地方。
代码如下:
// 代码三
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;}
}
这段代码看起来有点复杂,注意其中有两次if(instance==null)的判断,这个叫做『双重检查 Double-Check』。
- 第一个 if(instance==null),其实是为了解决代码二中的效率问题,只有instance为null的时候,才进入synchronized的代码段大大减少了几率。
- 第二个if(instance==null),则是跟代码二一样,是为了防止可能出现多个实例的情况。
这段代码看起来已经完美无瑕了。当然,只是『看起来』,还是有小概率出现问题的。想要充分理解需要先弄清楚以下几个概念:原子操作、指令重排。
原子操作
简单来说,原子操作(atomic)就是不可分割的操作,在计算机中,就是指不会因为线程调度被打断的操作。比如,简单的赋值是一个原子操作:
m = 6; // 这是个原子操作
假如m原先的值为0,那么对于这个操作,要么执行成功m变成了6,要么是没执行 m还是0,而不会出现诸如m=3这种中间态——即使是在并发的线程中。
但是,声明并赋值就不是一个原子操作:
int n=6;//这不是一个原子操作
对于这个语句,至少有两个操作:①声明一个变量n ②给n赋值为6——这样就会有一个中间状态:变量n已经被声明了但是还没有被赋值的状态。这样,在多线程中,由于线程执行顺序的不确定性,如果两个线程都使用m,就可能会导致不稳定的结果出现。
指令重排
简单来说,就是计算机为了提高执行效率,会做的一些优化,在不影响最终结果的情况下,可能会对一些语句的执行顺序进行调整。比如,这一段代码:
int a ; // 语句1
a = 8 ; // 语句2
int b = 9 ; // 语句3
int c = a + b ; // 语句4
正常来说,对于顺序结构,执行的顺序是自上到下,也即1234。但是,由于指令重排的原因,因为不影响最终的结果,所以,实际执行的顺序可能会变成3124或者1324。由于语句3和4没有原子性的问题,语句3和语句4也可能会拆分成原子操作,再重排。——也就是说,对于非原子性的操作,在不影响最终结果的情况下,其拆分成的原子操作可能会被重新排列执行顺序。
主要在于singleton = new Singleton()这句,这并非是一个原子操作,事实上在 JVM 中这句话大概做了下面 3 件事情。
1. 给 singleton 分配内存
2. 调用 Singleton 的构造函数来初始化成员变量,形成实例
3. 将singleton对象指向分配的内存空间(执行完这步 singleton才是非 null了)在JVM的即时编译器中存在指令重排序的优化。
也就是说上面的第二步和第三步的顺序是不能保证的,最终的执行顺序可能是 1-2-3 也可能是 1-3-2。如果是后者,则在 3 执行完毕、2 未执行之前,被线程二抢占了,这时 instance 已经是非 null 了(但却没有初始化),所以线程二会直接返回 instance,然后使用,然后顺理成章地报错。
再稍微解释一下,就是说,由于有一个『instance已经不为null但是仍没有完成初始化』的中间状态,而这个时候,如果有其他线程刚好运行到第一层if (instance ==null)这里,这里读取到的instance已经不为null了,所以就直接把这个中间状态的instance拿去用了,就会产生问题。这里的关键在于线程T1对instance的写操作没有完成,线程T2就执行了读操作。
对于代码三出现的问题,解决方案为:给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;}
}
volatile关键字的一个作用是禁止指令重排,把instance声明为volatile之后,对它的写操作就会有一个内存屏障,这样,在它的赋值完成之前,就不用会调用读操作。
注意:volatile阻止的不是singleton = new Singleton()这句话内部[1-2-3]的指令重排,而是保证了在一个写操作([1-2-3])完成之前,不会调用读操作(if (instance == null))。
双重检查(Double-Check)相关推荐
- 单例模式的两种实现方式对比:DCL (double check idiom)双重检查 和 lazy initialization holder class(静态内部类)...
首先这两种方式都是延迟初始化机制,就是当要用到的时候再去初始化. 但是Effective Java书中说过:除非绝对必要,否则就不要这么做. 1. DCL (double checked lockin ...
- 单例模式之双重检查锁(double check locking)的发展历程
不安全的单例 没有注意过多线程安全问题的时候,我们的单例可能是这样的: public final class Singleton {private static Singleton instance; ...
- Java中的双重检查锁(double checked locking)
起因 在实现单例模式时,如果未考虑多线程的情况,很容易写出下面的代码(也不能说是错误的): public class Singleton {private static Singleton uniqu ...
- java 双重检查锁 有序_Java中的双重检查锁(double checked locking)
1 public classSingleton {2 private staticSingleton uniqueSingleton;3 4 privateSingleton() {5 }6 7 pu ...
- 双重检查锁Double Checked Locking Pattern的非原子操作下的危险性
Double Checked Locking Pattern 即双重检查锁模式. 双重检查锁模式是一种软件设计模式,用于减少获取锁的开销.程序首先检查锁定条件,并且仅当检查表明需要锁时才才获取锁. 延 ...
- 有关“双重检查锁定失效”的说明
双重检查锁定(以下称为DCL)已被广泛当做多线程环境下延迟初始化的一种高效手段. 遗憾的是,在Java中,如果没有额外的同步,它并不可靠.在其它语言中,如c++,实现DCL,需要依赖于处理器的内存模型 ...
- Java中的双重检查锁定
在本文中,我们将介绍在RxJava中创建Singleton对象的一些技术. 最重要的是,我们将学习Java中的双重检查锁定 . Java中的Singleton模式是一种创新模式. 随着时间的流逝,人们 ...
- 双重检查锁单例模式为什么要用volatile关键字?
前言 从Java内存模型出发,结合并发编程中的原子性.可见性.有序性三个角度分析volatile所起的作用,并从汇编角度大致说了volatile的原理,说明了该关键字的应用场景:在这补充一点,分析下v ...
- 双重检查锁为什么要使用volatile字段?
前言 从Java内存模型出发,结合并发编程中的原子性.可见性.有序性三个角度分析volatile所起的作用,并从汇编角度大致说了volatile的原理,说明了该关键字的应用场景:在这补充一点,分析下v ...
- muduo源码剖析——Singleton单例模式之懒汉模式与DCL双重检查
0 懒汉与饿汉 对于Singleton单例模式我们并不陌生,但我们常用的多是饿汉模式: Singleton实例的声明和实例化在instance()函数中同时完成. 而懒汉模式要求,Singleton实 ...
最新文章
- 基于消息队列 RocketMQ 的大型分布式应用上云最佳实践
- Linux 内存管理 | 物理内存管理:物理内存、内存碎片、伙伴系统、slab分配器
- ConcurrentLinkedQueue常用方法
- 通过css样式,控制文字显示...
- excel2010设置列宽为像素_如何以厘米为单位精确设置Excel表格的行高列宽?
- 区块链技术的应用价值了解下
- 更新:2022 京东双11活动一键自动完成任务脚本app来了
- Android软件开发面试题,安卓面试题库
- 顶级“黑客”能厉害到什么地步?无信号也能上网,专家:高端操作!
- scrapy 抓取拉钩 ajax
- 【笑小枫的按步照搬系列】Git从安装到入门操作,一文搞定
- python的线程如何启用和结束_python线程进程
- 虚拟服务器 磊科,磊科无线路由器上DHCP服务器如何设置
- 《共同基金常识》书中的精髓:如何用好指数基金,做好理财投资?
- python酒店评论分析_GitHub - huangpd/senti_analysis: 利用Python实现酒店评论的中文情感分析...
- 阿根廷将大幅增加谷物产量
- 鼠标DPI和液晶显示器分辨率的关系
- LogisticRegressionCV作图
- [按键精灵]----卡尔智能改键(测试版)----更新日志
- 西门子S7-1200控制5轴伺服程序案例 PTO伺服轴脉冲定位控制功能应用+速度模式应用+扭矩模式应用