前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >单例模式-双重检查锁(DCL)和volatile 的应用

单例模式-双重检查锁(DCL)和volatile 的应用

作者头像
iiopsd
发布2022-12-23 08:52:58
6800
发布2022-12-23 08:52:58
举报
文章被收录于专栏:iiopsd技术专栏iiopsd技术专栏

单例模式-双重检查锁(DCL, 即 double-checked locking)

代码示例如下:

代码语言:javascript
复制
package com.hsy.demo;

/**
 * 懒汉单例
 *
 * 优点:懒加载,线程安全,效率较⾼
 * 缺点:实现较复杂
 *
 * @author 
 * @date 2022-06-22 10:08:26
 */
public class LazySingletonDCL {
    /**
     * 私有构造函数
     *
     * @param
     * @author Huangshaoyang
     * @date 2022-04-01 11:04:10
     * @since
     */
    private LazySingletonDCL() {
    }

    /**
     * 定义⼀个静态变量指向⾃⼰类型
     *
     * volatile 避免DCL 失效
     */
    private volatile static LazySingletonDCL lazySingleton = null;


    /**
     * 获取实例方法
     *
     * @param
     * @author Huangshaoyang
     * @date 2022-04-01 10:04:33
     * @since
     */
    public static LazySingletonDCL getLazySingleton() {
        // 第⼀重检查是否为 null
        if (lazySingleton == null) {
            // 使⽤ synchronized 加锁
            synchronized (LazySingletonDCL.class) {
                // 第二重检查是否为 null
                if (lazySingleton == null) {
                    lazySingleton = new LazySingletonDCL();
                }
            }
        }
        return lazySingleton;
    }

}

解释说明

优点:懒加载,线程安全,效率较⾼ 缺点:实现较复杂

这⾥的双重检查是指两次⾮空判断,锁指的是 synchronized 加锁,为什么要进⾏双重判断,其实很简单,第⼀重判断,如果实例已经存在,那么就不再需要进⾏同步操作,⽽是直接返回这个实例,如果没有创建,才会进⼊同步块,同步块的⽬的与之前相同,⽬的是为了防⽌有多个线程同时调⽤时,导致⽣成多个实例,有了同步块,每次只能有⼀个线程调⽤访问同步块内容,当第⼀个抢到锁的调⽤获取了实例之后,这个实例就会被创建,之后的所有调⽤都不会进⼊同步块,直接在第⼀重判断就返回单例。 关于内部的第⼆重空判断的作⽤,当多个线程⼀起到达锁位置时,进⾏锁竞争,其中⼀个线程获取锁,如果是第⼀次进⼊则为 null,会进⾏单例对象的创建,完成后释放锁,其他线程获取锁后就会被空判断拦截,直接返回已创建的单例对象。

其中最关键的⼀个点就是 volatile 关键字的使⽤,关于 volatile 的详细介绍可以直接搜索 volatile 关键字即可,有很多写的⾮常好的⽂章,这⾥不做详细介绍。简单说明⼀下,双重检查锁中使⽤ volatile 的两个重要特性:可⻅性、禁⽌指令重排序

这⾥为什么要使⽤ volatile ?

这是因为 new 关键字创建对象不是原⼦操作,创建⼀个对象会经历下⾯的步骤:

  1. 在堆内存开辟内存空间
  2. 调⽤构造⽅法,初始化对象
  3. 引⽤变量指向堆内存空间

对应字节码指令如下:

在这里插入图片描述
在这里插入图片描述

为了提⾼性能,编译器和处理器常常会对既定的代码执⾏顺序进⾏指令重排序,从源码到最终执⾏指令会经历如下流程: 1、源码 2、编译器优化重排序 3、指令级并⾏重排序 4、内存系统重排序 5、最终执⾏指令序列

所以经过指令重排序之后,创建对象的执⾏顺序可能为 1 2 3 或者 1 3 2 ,因此当某个线程在乱序运⾏ 1 3 2 指令的时候,引⽤变量指向堆内存空间,这个对象不为 null,但是没有初始化,其他线程有可能这个时候进⼊了 getInstance 的第⼀个 if(instance == null) 判断不为 nulll ,导致错误使⽤了没有初始化的⾮ null 实例,这样的话就会出现异常,这个就是DCL 失效问题。

当我们在引⽤变量上⾯添加 volatile 关键字以后,会通过在创建对象指令的前后添加内存屏障来禁⽌指令重排序,就可以避免这个问题,⽽且对volatile 修饰的变量的修改对其他任何线程都是可⻅的

本文参与 腾讯云自媒体分享计划,分享自作者个人站点/博客。
原始发表:2022-06-22,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 单例模式-双重检查锁(DCL, 即 double-checked locking)
  • 解释说明
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档