Java单例-双重检查锁
问题引入
Java中实现单例模式,一般性的做法是如下方式:
class Singleton {
private static Singleton INSTANCE = null;
private Singleton() {}
public static getInstance() {
if (null == INSTANCE) { // <-- 此处如果有多个执行流同时进入,会造成多次初始化
INSTANCE = new Singleton();
}
return INSTANCE;
}
}
上述代码中,第6行处,对单例对象INSTANCE进行判空检查,如果为null,则进行初始化。
这一步在单执行流的逻辑上是没有问题的。但是当多个执行流同时运行到此处时,如果执行流a正在初始化Singleton对象,还没返回其引用,就被调度出去了,此时执行流b也会进入此处,再次对Singleton对象进行初始化。如此一来,JVM中就会存在多个Singleton实例。
因此,第7行的Singleton初始化代码块,应当作为临界区,对其访问需要加锁同步。
初步解决方案
class Singleton {
private static Singleton INSTANCE = null;
private Singleton() {}
public static getInstance() {
if (null == INSTANCE) { // <-- 第1次,一般性检查,但是有并发隐患:可能有多执行流同时进入改处
synchronized(Singleton.class) {
if (null == INSTANCE) { // <-- 此处第2次检查,为了防止后续多执行流并发时,后续获取同步锁的执行流,不会再次初始化Singleton对象
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
如上,第1次检查,用来判断是否需要对Singleton进行初始化;如果是,则先加同步锁(此时可能有多个执行流都运行到改处);获得锁之后,第2次检查Singleton对象是否已被其他并发的执行流初始化了(这个null判空检查有隐患,后续阐明);如果两次检查都通过,则表明当前执行流,是第一个进入临界区的,因此可以担负对Singleton对象初始化的责任。由于同步加锁及第2次检查的存在,后续其他的执行流,即使同时进入临界区外等待,也不会出现对Singleton对象多次初始化的问题。
以上,应该是比较完美的解决方案了。
但是,
由于对象初始化的过程并不是原子的指令,无法在单个指令周期完成,又Java编译器对指令重排序优化的存在,对象初始化的操作流程会发生变化:
原始流程:
op1:分配内存空间
op2:初始化对象
op3:将对象的引用,指向分配的内存
指令重排序优化之后的流程:
op1:分配内存空间
op2:将对象的引用,指向分配的内存
op3:初始化对象
由于对象初始化流程的非原子性,当前执行流很可能在新流程的op2->op3这一步被调度出去,进而导致JVM中存在着一个已开辟内存空间、但是未初始化的Singleton实例。如果此时,其他调度进来的执行流使用了这个残缺的Singleton实例,很有可能因为数据异常引发运行时错误。
完善后的解决方案
为此,我们需要一个机制,来阻止编译器对指令的重排序——这就是关键字 volatile。
加了 volatile 关键字的变量,编译器不会对其初始化指令进行重排序优化。因此就避免了上述的问题发生。
class Singleton {
private static volatile Singleton INSTANCE = null; // <-- 禁止指令重排序
private Singleton() {}
public static getInstance() {
if (null == INSTANCE) { // <-- 第1次,一般性检查,但是有并发隐患:可能有多执行流同时进入改处
synchronized(Singleton.class) {
if (null == INSTANCE) { // <-- 此处第2次检查,为了防止后续多执行流并发时,后续获取同步锁的执行流,不会再次初始化Singleton对象
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
后记
我还想到一个不利用 Java 的 volatile 特性的方案:
class Singleton {
private static Singleton INSTANCE = null;
private static constructed = false; // <-- 用一个标记变量
private Singleton() {}
public static getInstance() {
if (!constructed) { // <-- 第1次,一般性检查,但是有并发隐患:可能有多执行流同时进入改处
synchronized(Singleton.class) {
if (!constructed) { // <-- 此处第2次检查,为了防止后续多执行流并发时,后续获取同步锁的执行流,不会再次初始化Singleton对象
INSTANCE = new Singleton();
constructed = true; // <-- 我没有探究这里,会不会出现指令重排序的情况
}
}
}
return INSTANCE;
}
}
Java单例-双重检查锁的更多相关文章
- Java中的双重检查锁(double checked locking)
最初的代码 在最近的项目中,写出了这样的一段代码 private static SomeClass instance; public SomeClass getInstance() { if (nul ...
- 从学习“单例模式”学到的Java知识:双重检查锁和延迟初始化
一切真是有缘,上午刚刚看完单例模式,还在为其中的代码块同步而兴奋,下午就遇见这篇文章:双重检查锁定与延迟初始化.我一看,文章开头语出惊人,说这是一种错误的优化,我说,难道上午学的东西下午就过时了吗?仔 ...
- 单例模式中用volatile和synchronized来满足双重检查锁机制
背景:我们在实现单例模式的时候往往会忽略掉多线程的情况,就是写的代码在单线程的情况下是没问题的,但是一碰到多个线程的时候,由于代码没写好,就会引发很多问题,而且这些问题都是很隐蔽和很难排查的. 例子1 ...
- 双重检查锁实现单例(java)
单例类在Java开发者中非常常用,但是它给初级开发者们造成了很多挑战.他们所面对的其中一个关键挑战是,怎样确保单例类的行为是单例?也就是说,无论任何原因,如何防止单例类有多个实例.在整个应用生命周期中 ...
- Java基础教程:多线程杂谈——双重检查锁与Volatile
Java基础教程:多线程杂谈——双重检查锁与Volatile 双重检查锁 有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化.此时程序员可能会采用延迟初始化.但要正确实 ...
- 【Java学习笔记】线程安全的单例模式及双重检查锁—个人理解
搬以前写的博客[2014-12-30 16:04] 在web应用中服务器面临的是大量的访问请求,免不了多线程程序,但是有时候,我们希望在多线程应用中的某一个类只能新建一个对象的时候,就会遇到问题. 首 ...
- Java盲点:双重检查锁定及单例模式
尊重原创: http://gstarwd.iteye.com/blog/692937 2004 年 5 月 01 日 所有的编程语言都有一些共用的习语.了解和使用一些习语很有用,程序员们花费宝贵的时间 ...
- 写一个安全的Java单例
单例模式可能是我们平常工作中最常用的一种设计模式了.单例模式解决的问题也很常见,即如何创建一个唯一的对象.但想安全的创建它其实并不容易,还需要一些思考和对JVM的了解. 1.首先,课本上告诉我,单例这 ...
- 对象部分初始化:原理以及验证代码(双重检查锁与volatile相关)
对象部分初始化:原理以及验证代码(双重检查锁与volatile相关) 对象部分初始化被称为 Partially initialized objects / Partially constructed ...
随机推荐
- RHCAS_DAY01
世界三大云厂商: 第一:亚马逊,AWS 第二:微软,Azure 第三:中国,阿里云,在全球15个地区建立的200多个数据中心 https://www.aliyun.com/ #阿里云地址 云计算三种模 ...
- 《Python Cookbook v3.0.0》Chapter1 数据结构和算法
感谢: https://github.com/yidao620c/python3-cookbook 如有侵权,请联系我整改. 本文章节会严格按照原书(以便和原书对照,章节标题可能会略有修改),内容会有 ...
- 图 邻接表 邻接矩阵 BFS生成树 DFS生成树
- 黑马JVM教程——自学笔记(二)
三.垃圾回收 3.1.如何判断对象可以回收 3.1.1 引用计数法 弊端:循环引用时,两个对象的计数都为1,导致两个对象都无法被释放 3.1.2 可达性分析算法 JVM中的垃圾回收器通过可达性分析来探 ...
- 实现动态加载一个 JavaScript 资源
var script = document.createElement("script"); var head = document.getElementsByTagName(&q ...
- C#:[StructLayout(LayoutKind.Sequential)]
参考网址: https://blog.csdn.net/hongkonglife/article/details/23422857 结构体是由若干成员组成的.布局有两种1.Sequential,顺序布 ...
- ASP.NET Core:中间件
一.什么是中间件 我们都知道,任何的一个web框架都是把http请求封装成一个管道,每一次的请求都是经过管道的一系列操作,最终才会到达我们写的代码中.而中间件就是用于组成应用程序管道来处理请求和响应的 ...
- .Net Core 踩坑记录--程序独立发布 无法运行
背景 创建.net Core3.1 的Console程序 点击发布 选择独立部署模式 目标电脑 Win10 x64 未安装任何.Net SDK 现象 发布的程序 点击运行没有反应 或是直接闪退 解决 ...
- C#中的集合类
集合相当于容器,用于将一系列相似的项组合在一起. 集合可以分为泛型集合类和非泛型集合类. 多数集合类都是派生自ICollection.IComparer.IEnumerable.IList.IDict ...
- C#异步编程 Task await的理解
async/await是C#5.0中推出的,先上用法: static void Main(string[] args) { Console.WriteLine("-------主线程启动-- ...