双重检查锁定模式Java中的陷阱案例
作者:onlythinking 发布时间:2023-11-13 22:11:02
1、简介
双重检查锁定(也叫做双重检查锁定优化)是一种软件设计模式。
它的作用是减少延迟初始化在多线程环境下获取锁的次数,尤其是单例模式下比较突出。
软件设计模式:解决常用问题的通用解决方案。编程中针对一些常见业务固有的模版。
延迟初始化:在编程中,将对象的创建,值计算或其他昂贵过程延迟到第一次使用时进行。
单例模式:在一定范围内,只生成一个实例对象。
2、Java中的双重检查锁定
单例模式我们需保证实例只初始化一次。
下面例子在单线程环境奏效,多线程环境下会有线程安全问题(instance
被初始化多次)。
private static Singleton instance;
public static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
下面例子主要是性能问题。首先加锁操作开销很大,因为线程安全发生在对象初始化,而这里做了做了全局控制,造成浪费。
public synchronized static Singleton getInstance() {
if (null == instance) {
instance = new Singleton();
}
return instance;
}
为了控制线程安全又能保证性能,双重检查锁定模式出现。
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
逻辑如下:
我们分析一下执行逻辑:
假设有三个线程 T1 T2 T3
,依次访问 getInstance
方法。
T1
第一次检查为Null
进入同步块,T1持有锁,第二次检查为Null 执行对象创建。T2
第一次检查为Null
进入同步块,T2等待T1释放锁,锁释放后,T2进入执行第二次检查不为Null,返回实例对象。T3
第一次检查不为Null
,直接返回对象。
上面一切似乎很完美,但是这里面存在陷阱。根据Java
内存模型我们知道,编译器优化处理会进行重排序。
instance = new Singleton()
大体分两个步骤;
1 创建初始化对象;
2 引用赋值。
而 1 2 步骤可能颠倒,会造成对象属性在初始化前调用的错误。
private static Singleton instance;
...
instance = new Singleton();
...
public class Singleton {
private int age;
public Singleton() {
this.age = 80;
}
}
这种细微的错误不容易出现,但是它的确存在。大家可以参考下面这份报告,里面详细记录这个问题。
http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
3、列举方案
报告里面也列举了几种解决方案
3.1 利用 ThreadLocal
private static final ThreadLocal<Singleton> threadInstance = new ThreadLocal<>();
public static Singleton getInstance() {
if (null == threadInstance.get()) {
createInstance();
}
return instance;
}
private static void createInstance() {
synchronized (Singleton.class) {
if (instance == null)
instance = new Singleton();
}
threadInstance.set(instance);
}
3.2 利用volatile(解决重排序问题)
private volatile static Singleton instance;
public static Singleton getInstance() {
if (null == instance) {
synchronized (Singleton.class) {
if (null == instance) {
instance = new Singleton();
}
}
}
return instance;
}
下面是不同方案下的性能比较报告
http://www.cs.umd.edu/~pugh/java/memoryModel/DCL-performance.html
4、总结
本章节主要记录了双重检查锁定模式使用中应该注意的细微事项。
来源:https://www.onlythinking.com/2020/06/01/%E5%B0%8F%E5%BF%83java%E5%8F%8C%E9%87%8D%E6%A3%80%E6%9F%A5%E9%94%81%E5%AE%9A%E6%A8%A1%E5%BC%8F%E4%B8%AD%E9%99%B7%E9%98%B1/


猜你喜欢
- 在Android中,Activity主要负责前台页面的展示,Service主要负责需要长期运行的任务,所以在我们实际开发中,就会常常遇到Ac
- 1.图的遍历从图中某一顶点出发访问图中其余顶点,且每个顶点仅被访问一次图的遍历有两种深度优先遍历DFS、广度优先遍历BFS2.深度优先遍历深
- SpringBoot JPA分页查询指定列并返回指定实体用习惯Mybatis,没用过jpa 真是各种踩坑了脑壳疼,一个分页弄老半天,原来就一
- 面向方面编程(Aspect Oriented Programming,简称AOP)是一种声明式编程(Declarative Programm
- 该配置基于IDEA2020.1版本,如后续有版本更新或者配置变更,再更新idea64.exe.vmoptions配置为提供IDEA启动速度和
- 1 使用Office自带的库前提是本机须安装office才能运行,且不同的office版本之间可能会有兼容问题,从Nuget下载 Micro
- spring cloud zuul增加header传输在使用OAuth2.0传输权限认证,为了再调用其他的项目的时候获取token,必须在t
- Java Comparable 和 Comparator 的详解及区别Java 中为我们提供了两种比较机制:Comparable 和 Com
- 微信公众号开发之回复图文消息,供大家参考,具体内容如下图文消息的主要参数说明通过微信官方的消息接口指南,可以看到对图文消息的参数介绍,如下图
- 1 SeekBar简介SeekBar是进度条。我们使用进度条时,可以使用系统默认的进度条;也可以自定义进度条的图片和滑块图片等。2 Seek
- 添加标题在 Winfrom 界面中添加一个 ListView 组件,然后点击右上角的箭头,点击编辑列添加下面标题,然后点击确定此时 List
- TTL简介TTL 是什么呢?TTL 是 RabbitMQ 中一个消息或者队列的属性,表明一条消息或者该队列中的所有消息的最大存活时间,单位是
- namespace ConsoleApplication1{ using System; &n
- 输入方法第一种输入方法:scannerimport java.util.Scanner; // 导入java.util.Scannerpub
- 本文实例讲述了Java实现接口的枚举类。分享给大家供大家参考,具体如下:一 点睛枚举类也可以实现一个或多个接口。与普通类实现一个或多个接口完
- 前言想使用ffmpeg打开摄像头,需要输入摄像头的名称,而ffmpeg本身的枚举摄像头列表功能不是接口,所以需要用其他方式获取到设备列表。C
- 在 C 语言中,如果发生错误,上级函数要进行出错处理,层层上传,容易造成过多的出错处理代码,并且传递的效率比较低下。C++ 中的异常C++
- 基本要点1、定义根据百度百科的定义,RESTFUL是一种网络应用程序的设计风格和开发方式2、传统方式与Restful风格的区别在我们学习re
- 1、声明一个测试对象import java.time.LocalDate;import java.util.List;import lomb
- 一、简介Spring Boot是由Pivotal团队提供的全新框架,其设计目的是用来简化新spring应用的初始搭建以及开发过程。该框架使用