问题引入

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单例-双重检查锁的更多相关文章

  1. Java中的双重检查锁(double checked locking)

    最初的代码 在最近的项目中,写出了这样的一段代码 private static SomeClass instance; public SomeClass getInstance() { if (nul ...

  2. 从学习“单例模式”学到的Java知识:双重检查锁和延迟初始化

    一切真是有缘,上午刚刚看完单例模式,还在为其中的代码块同步而兴奋,下午就遇见这篇文章:双重检查锁定与延迟初始化.我一看,文章开头语出惊人,说这是一种错误的优化,我说,难道上午学的东西下午就过时了吗?仔 ...

  3. 单例模式中用volatile和synchronized来满足双重检查锁机制

    背景:我们在实现单例模式的时候往往会忽略掉多线程的情况,就是写的代码在单线程的情况下是没问题的,但是一碰到多个线程的时候,由于代码没写好,就会引发很多问题,而且这些问题都是很隐蔽和很难排查的. 例子1 ...

  4. 双重检查锁实现单例(java)

    单例类在Java开发者中非常常用,但是它给初级开发者们造成了很多挑战.他们所面对的其中一个关键挑战是,怎样确保单例类的行为是单例?也就是说,无论任何原因,如何防止单例类有多个实例.在整个应用生命周期中 ...

  5. Java基础教程:多线程杂谈——双重检查锁与Volatile

    Java基础教程:多线程杂谈——双重检查锁与Volatile 双重检查锁 有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化.此时程序员可能会采用延迟初始化.但要正确实 ...

  6. 【Java学习笔记】线程安全的单例模式及双重检查锁—个人理解

    搬以前写的博客[2014-12-30 16:04] 在web应用中服务器面临的是大量的访问请求,免不了多线程程序,但是有时候,我们希望在多线程应用中的某一个类只能新建一个对象的时候,就会遇到问题. 首 ...

  7. Java盲点:双重检查锁定及单例模式

    尊重原创: http://gstarwd.iteye.com/blog/692937 2004 年 5 月 01 日 所有的编程语言都有一些共用的习语.了解和使用一些习语很有用,程序员们花费宝贵的时间 ...

  8. 写一个安全的Java单例

    单例模式可能是我们平常工作中最常用的一种设计模式了.单例模式解决的问题也很常见,即如何创建一个唯一的对象.但想安全的创建它其实并不容易,还需要一些思考和对JVM的了解. 1.首先,课本上告诉我,单例这 ...

  9. 对象部分初始化:原理以及验证代码(双重检查锁与volatile相关)

    对象部分初始化:原理以及验证代码(双重检查锁与volatile相关) 对象部分初始化被称为 Partially initialized objects / Partially constructed ...

随机推荐

  1. MySQL 事务、日志、锁、索引学习总结,

    MySQL架构 MySQL可分为Server和存储引擎两部分,如图1所示. Server层:包括客户端连接器.查询缓存.解析/预处理器.优化器.执行器等,以及MySQL内置函数和所有跨引擎的功能都在这 ...

  2. 雪花算法ID在前端丢失精度解决方案

    首先说一下背景,目前笔者的工作是物联网方面的,设备有对应的智慧运营平台,平台开发中建表的主键用的是Mybatis plus默认的雪花算法来生成的,也就是分布式系统比较常用的雪花ID,技术栈就是常用的S ...

  3. [TensorFow2.0]-MNIST手写数字识别

    本人人工智能初学者,现在在学习TensorFlow2.0,对一些学习内容做一下笔记.笔记中,有些内容理解可能较为肤浅.有偏差等,各位在阅读时如有发现问题,请评论或者邮箱(右侧边栏有邮箱地址)提醒. 若 ...

  4. Grid布局如何设置动画效果

    CS代码 新增 GridLengthAnimation继承自AnimationTimeline public class GridLengthAnimation : AnimationTimeline ...

  5. Alibaba-技术专区-RocketMQ 延迟消息实现原理和源码分析

    痛点背景 业务场景 假设有这么一个需求,用户下单后如果30分钟未支付,则该订单需要被关闭.你会怎么做? 之前方案 最简单的做法,可以服务端启动个定时器,隔个几秒扫描数据库中待支付的订单,如果(当前时间 ...

  6. Vulnhub -- DC4靶机渗透

    用nmap扫描ip和端口,发现只开启了22ssh端口和80http端口 打开网页只有一个登录界面 目录爆破没有发现什么有用的,尝试对登录进行弱口令爆破 一开始使用burpsuite,使用一个小字典进行 ...

  7. 题解 数列 及exgcd总结

    传送门 自闭了--考场上exgcd打错然后对着屏幕自闭了一个小时不知道它为什么解得不对 开始恶补: 对于方程 \(a*x+b*y=c\) ,就等价于 \(a*x \equiv c\pmod{b}\) ...

  8. element ui loading加载开启与关闭

    参考:https://blog.csdn.net/qq_41877107/article/details/87690555 Vue项目引入element-ui,之后,将以下代码写入 mounted() ...

  9. springboot与通用mapper的整合

    找到springboot工程下的pom.xml文件,导入如下的依赖jar包 <!--配置通用Mapper start--> <dependency> <groupId&g ...

  10. WPF---数据绑定之ValidationRule数据校验(六)

    一.概述 我们知道,Binding好比架设在Source和Target之间的桥梁,数据可以借助这个桥梁进行流通.在数据流通的过程中,我们可以在Binding这座桥梁上设置关卡,对数据的有效性进行验证. ...