当前位置: 首页 > news >正文

双重检查锁DCL对象半初始化问题?

双重检查锁(Double-Checked Locking Pattern, DCL)是一种用于实现延迟初始化(Lazy Initialization)的线程安全模式,尤其在单例模式中常见

1. 双重检查锁的基本实现

以下是一个典型的双重检查锁实现单例模式的代码:

public class Singleton {
    private static Singleton instance;

    private Singleton() {
        // 私有构造函数,防止外部实例化
    }

    public static Singleton getInstance() {
        if (instance == null) { // 第一次检查
            synchronized (Singleton.class) { // 加锁
                if (instance == null) { // 第二次检查
                    instance = new Singleton(); // 创建实例
                }
            }
        }
        return instance;
    }
}

工作原理:

    第一次检查:判断 instance 是否为 null,如果已经初始化,则直接返回。
    加锁:如果 instance 为 null,进入同步块。
    第二次检查:再次确认 instance 是否为 null,避免多线程同时进入同步块时重复创建实例。
    创建实例:只有在 instance 确实为 null 时才创建实例。

这种方式的优点是减少锁的开销,因为只有在第一次创建实例时才会进入同步块,后续调用不会加锁。

2. 对象半初始化问题

尽管上述代码看似合理,但在某些情况下,它可能会导致对象半初始化问题,具体表现为:

    某些线程可能会看到一个未完全初始化的对象。

原因分析:

在 Java 中,对象的创建过程并不是原子性的,而是分为多个步骤:

    分配内存空间。
    初始化对象(调用构造函数)。
    将分配的内存地址赋值给引用变量(即 instance)。

由于编译器或处理器为了优化性能,可能会对这些步骤进行指令重排序,实际执行顺序可能变为:

    分配内存空间。
    将内存地址赋值给引用变量(即 instance)。
    初始化对象。

这种重排序会导致以下情况:

    线程 A 在创建对象时,先将内存地址赋值给了 instance,但此时对象尚未完成初始化。
    线程 B 进入 getInstance() 方法时,发现 instance 不为 null,于是直接返回了这个未完全初始化的对象。

这会导致线程 B 使用了一个未完全初始化的对象,从而引发不可预期的错误。

3. 如何解决对象半初始化问题?

为了避免指令重排序带来的问题,可以通过以下方式解决:


1)使用 volatile 关键字

在 Java 5 及之后的版本中,可以使用 volatile 关键字来修饰 instance,以防止指令重排序:

public class Singleton {
    private static volatile Singleton instance;

    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

作用:

    volatile 禁止了指令重排序,确保对象的初始化过程按照正确的顺序执行。
    同时,volatile 保证了可见性,当一个线程修改了 instance 的值后,其他线程能够立即看到最新的值。

2)使用静态内部类(推荐)

另一种更优雅的方式是使用静态内部类来实现单例模式。这种方法利用了 Java 类加载机制的线程安全性,避免了显式的同步和 volatile:

public class Singleton {
    private Singleton() {}

    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }

    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
}

优点:

    静态内部类在第一次被加载时才会初始化 INSTANCE,实现了延迟加载。
    Java 的类加载机制本身是线程安全的,因此不需要额外的同步。

3)使用 enum 实现单例(最简洁的方式)

从 Java 的角度来看,enum 是实现单例的最佳方式之一。它不仅简单、线程安全,还天然防止反射攻击和序列化问题:

public enum Singleton {
    INSTANCE;

    public void doSomething() {
        // 单例的功能
    }
}

优点:

    枚举类型的单例是 JVM 保证的,无需担心多线程问题。
    天然防止反射攻击和序列化问题。

4. 总结

    对象半初始化问题的根本原因是指令重排序,可能导致线程看到未完全初始化的对象。
    解决方法包括:
        使用 volatile 关键字禁止指令重排序。
        使用静态内部类实现单例模式。
        使用 enum 实现单例模式(推荐)。

http://www.xdnf.cn/news/136207.html

相关文章:

  • pnpm monoreop 打包时 node_modules 内部包 typescript 不能推导出类型报错
  • 电力系统惯性与惯量关系解析
  • day003
  • 你的图数传模块该换了!
  • Python Transformers 库介绍
  • RHEL与CentOS:从同源到分流的开源操作系统演进
  • 简述:变更调查的历史情况
  • 计算机网络核心知识点全解析(面试通关版)
  • 插入html文件,让数据可视化彰显高端大气-Excel易用宝
  • 安全编排自动化与响应(SOAR):从事件响应到智能编排的技术实践
  • cgroup sched_cfs_bandwidth_slice参数的作用及效果
  • 【5】GD32 基础通信外设:USART、I2C、SPI
  • 【playwright】 page.get_by_类型方法
  • 【RedisLockRegistry】分布式锁
  • NS3-虚拟网络与物理网络的交互-2 FdNetDevice文件描述符网络设备
  • CMake ctest
  • 手搓传染病模型(SIR)
  • Git 入门知识详解
  • 人工智能与机器学习:Python从零实现逻辑回归模型
  • 【中级软件设计师】函数调用 —— 传值调用和传地址调用 (附软考真题)
  • 分享Matlab成功安装Support Package硬件支持包的方法
  • 第二章 信息技术发展(2.1 信息技术及其发展)
  • 达梦数据库运维
  • 常见缓存淘汰算法(LRU、LFU、FIFO)的区别与实现
  • MYSQL 常用字符串函数 和 时间函数详解
  • MyBatisPlus文档
  • 路由器的基础配置全解析:静态动态路由 + 华为 ENSP 命令大全
  • 一种专用车辆智能配电模块的设计解析:技术革新与未来展望
  • 京东以图搜图(拍立淘)API接口返回参数详解
  • ALTER TABLE 之痛 - 为何我们需要在线表结构变更?