双重检查锁定模式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;
}

逻辑如下:

双重检查锁定模式Java中的陷阱案例

我们分析一下执行逻辑:

假设有三个线程 T1 T2 T3 ,依次访问 getInstance 方法。

  • T1 第一次检查为Null 进入同步块,T1持有锁,第二次检查为Null 执行对象创建。

  • T2 第一次检查为Null 进入同步块,T2等待T1释放锁,锁释放后,T2进入执行第二次检查不为Null,返回实例对象。

  • T3 第一次检查不为Null,直接返回对象。

双重检查锁定模式Java中的陷阱案例

上面一切似乎很完美,但是这里面存在陷阱。根据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;
   }
}

这种细微的错误不容易出现,但是它的确存在。大家可以参考下面这份报告,里面详细记录这个问题。

双重检查锁定模式Java中的陷阱案例

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/

标签:双重检查锁定,Java,陷阱
0
投稿

猜你喜欢

  • JAVA图片水印开发案例详解

    2023-06-20 02:06:53
  • Java 数据结构与算法系列精讲之队列

    2023-09-26 21:10:31
  • Java最长公共子序列示例源码

    2023-08-20 13:25:37
  • Java农夫过河问题的继承与多态实现详解

    2022-07-12 16:13:22
  • Java实现五子棋的基础方法

    2021-07-11 12:32:08
  • SpringBoot项目鉴权的4种方式小结

    2021-10-23 20:10:05
  • Java并发编程之ConcurrentLinkedQueue源码详解

    2023-01-22 16:19:51
  • Java按照List内存储的对象的某个字段进行排序的实例

    2023-12-11 11:58:35
  • C#有效防止同一账号多次登录(附三种方法)

    2023-05-23 10:32:45
  • SpringBoot下如何实现支付宝接口的使用

    2023-11-06 14:26:15
  • Java操作FTP实现上传下载功能

    2021-12-07 18:35:04
  • C#延迟执行方法函数实例讲解

    2022-06-17 19:23:50
  • C#实现剪刀石头布游戏

    2021-11-10 05:19:32
  • 自定义Android注解系列教程之注解变量

    2022-10-17 05:42:10
  • SpringBoot整合TKMyBatis实现单表增删改查操作

    2022-01-30 19:52:28
  • C++双向循环列表用法实例

    2023-07-23 03:58:54
  • java数据库唯一id生成工具类

    2023-04-04 22:53:34
  • C#自定义音乐播放器进度条

    2023-07-04 21:54:05
  • init output stream初始化输出流源码分析

    2023-01-08 09:53:20
  • java 读写文件[多种方法]

    2022-10-04 09:09:00
  • asp之家 软件编程 m.aspxhome.com