双重校验锁 --使用volatile和两次判空校验
介绍
双重校验锁是单例模式中,饿汉式的一种实现方式。因为有两次判空校验,所以叫双重校验锁,一次是在同步代码块外,一次是在同步代码块内。
为什么在同步代码块内还要再检验一次?
第一个if减少性能开销,第二个if避免生成多个对象实例。
现有三个线程A,B,C,假设线程A和线程B同时调用getSingleton()时,判断第一层if判断都为空,这时线程A先拿到锁,线程B在代码块外层等待。线程A进行第二层if判断,条件成立后new了一个新对象,创建完成,释放锁,线程B拿到锁,进行第二层if判断,singleton不为空,直接返回singleton释放锁,避免生成多个对象实例。线程线C调用getSingleton时第一层判断不成立,直接拿到singleton对象返回,避免进入锁,减少性能开销。
为什么要用volatile关键字?
singleton = new Singleton();这行代码并不是一个原子指令,可能会在JVM中进行指令重排;
new 实例背后的指令,我们通过使用 javap -c指令,查看字节码如下:
// 创建 Singleton 对象实例,分配内存
0: new #5
// 复制栈顶地址,并再将其压入栈顶
3: dup
// 调用构造器方法,初始化 Singleton对象
4: invokespecial #6 // Method "<init>":()V
// 存入局部方法变量表
7: astore_1
从字节码可以看到创建一个对象实例,可以分为三步:
(1)分配对象内存(给singleton分配内存)。
(2)调用构造器方法,执行初始化(调用 Singleton 的构造函数来初始化成员变量)。
(3)将对象引用赋值给变量(执行完这步 singleton 就为非 null 了)。
在 JVM 的即时编译器中存在指令重排序的优化。指令重排并不影响单线程内的执行结果,但是在多线程内可能会影响结果。也就是说上面的2和3的顺序是不能保证的,但是并不会重排序 1 的顺序,因为 2,3 指令需要依托 1 指令执行结果。最终的执行顺序可能是 1-2-3 也可能是 1-3-2。
1-3-2的情况
上面多线程执行的流程中,如果线程A获取到锁进入创建对象实例,这个时候发生了指令重排序。当线程A 执行到 t3 时刻(singleton已经非null了,但是却没有初始化),此时线程 B 抢占了,由于此时singleton已经不为 Null,会直接返回 singleton对象,然后使用singleton对象,然而该对象还未初始化,就会报错。我们只需将 singleton 变量声明成 volatile 就可以禁止指令重排,避免这种现象发生。
参考/好文:
菜鸟教程 – 设计模式--https://www.runoob.com/design-pattern/singleton-pattern.html
掘金 --https://juejin.im/post/5d54c2d251882542f27bdff6
双重校验锁 --使用volatile和两次判空校验的更多相关文章
- Java基础教程:多线程杂谈——双重检查锁与Volatile
Java基础教程:多线程杂谈——双重检查锁与Volatile 双重检查锁 有时候可能需要推迟一些高开销的对象初始化操作,并且只有在使用这些对象时才进行初始化.此时程序员可能会采用延迟初始化.但要正确实 ...
- 对象部分初始化:原理以及验证代码(双重检查锁与volatile相关)
对象部分初始化:原理以及验证代码(双重检查锁与volatile相关) 对象部分初始化被称为 Partially initialized objects / Partially constructed ...
- 单例模式中用volatile和synchronized来满足双重检查锁机制
背景:我们在实现单例模式的时候往往会忽略掉多线程的情况,就是写的代码在单线程的情况下是没问题的,但是一碰到多个线程的时候,由于代码没写好,就会引发很多问题,而且这些问题都是很隐蔽和很难排查的. 例子1 ...
- 关于Java单例模式中双重校验锁的实现目的及原理
开始复习设计模式,一开始理解单例模式中的双重校验锁卡住了,想通了后就自己做了段思维导图来帮助自己理解. 其实理解下来并不难,但还是记录下来帮助自己回忆和借机试试养成写博客的习惯~ public cla ...
- 双重检验锁模式为什么要使用volatile?
并发编程情况下有三个要点:操作的原子性.可见性.有序性. volatile保证了可见性和有序性,但是并不能保证原子性. 首先看一下DCL(双重检验锁)的实现: public class Singlet ...
- Java中单例七种写法(懒汉、恶汉、静态内部类、双重检验锁、枚举)
/*** * 懒汉模式 1 * 可以延迟加载,但线程不安全. * @author admin * */ public class TestSinleton1 { private static Test ...
- Java中的双重检查锁(double checked locking)
最初的代码 在最近的项目中,写出了这样的一段代码 private static SomeClass instance; public SomeClass getInstance() { if (nul ...
- 单例模式双重检验锁的判断是否为null的意义
关于双重检验锁首先简单来看一个小例子: public class Singleton{ private static Singleton instance = null; private Single ...
- 双重检查锁实现单例(java)
单例类在Java开发者中非常常用,但是它给初级开发者们造成了很多挑战.他们所面对的其中一个关键挑战是,怎样确保单例类的行为是单例?也就是说,无论任何原因,如何防止单例类有多个实例.在整个应用生命周期中 ...
随机推荐
- Jemter环境搭建
Jemter环境搭建 步骤一:安装Jmeter 1.下载Jmeter,官网地址:http://jmeter.apache.org/download_jmeter.cgi 2.解压Jmeter安装包,J ...
- Dell R740 使用U盘安装 CentOS7.4 出现Warning:dracut-initqueue timeout - starting timeout scripts解决办法
使用使用UltraISO软碟通刻录U盘,然后在Dell R740服务器安装CentOS7.4会出现如下错误: 解决办法: 1.使用blkid确认U盘的盘符,截图如下: 2.按F11键重启 3.进入启动 ...
- SNOI2020 部分题解
D1T1 画图可以发现,多了一条边过后的图是串并联图.(暂时不确定) 然后我们考虑把问题变成,若生成树包含一条边\(e\),则使生成树权值乘上\(a_e\),否则乘上\(b_e\),求最终的生成树权值 ...
- SpringBoot快速入门(实战篇一)
SpringBoot快速入门(一) 一SpringBoot简介 1.spring开发经历的阶段 Spring 诞生时是 Java 企业版(Java Enterprise Edition,JEE,也称 ...
- JavaSE18-字节缓冲流&字符流
1.字节缓冲流 1.1 字节缓冲流构造方法 字节缓冲流介绍 BufferOutputStream:该类实现缓冲输出流. 通过设置这样的输出流,应用程序可以向底层输出流写 入字节,而不必为写入的每个字节 ...
- 去掉RedisDesktopManager更新提示弹窗
去掉RedisDesktopManager更新提示弹窗 起因 每次打开RDM都要弹出一个更新提示弹窗,虽然打开次数不频繁,总是有个弹窗再点一次OK按钮,还不能设置关闭更新检查.更新下载还要各种登录麻烦 ...
- Oracle数据导入Mysql中
一.Navicat Premium中的数据迁移工具 为了生产库释放部分资源,需要将API模块迁移到mysql中,及需要导数据. 尝试了oracle to mysql工具,迁移时报错不说,这么大的数据量 ...
- vue第十单元(动态组件 keep-alive(钩子函数) 递归组件(name) 组件命名约定)
第十单元(动态组件 keep-alive(钩子函数) 递归组件(name) 组件命名约定) #课程目标 熟练掌握动态组件的实现 掌握keep-alive缓存组件,以及相应的钩子函数 熟练掌握递归组件, ...
- Spring Cloud Ribbon之URL重构(三)
接着前面的说,前两篇中分析了解析和动态服务列表的获取,这两步完成后那接下来要做的事就是重组解析后的URL路径和发起通信了,这一步完成应该是在前面分析的RibbonLoadBalancerClient. ...
- JAVA基础--cmd命令
常用cmd命令行,很简单 md(make directory):创建文件夹 如:md test1: rd(remove directory):删除空文件夹 如:rd test1:若文件夹包含其他文件, ...