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

【自存】懒汉式单例模式中的多线程经典问题

单例模式

单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。这一点在很多场景上都需要。比如 JDBC 中的 DataSource 实例就只需要一个。单例模式的具体实现方法又分为饿汉懒汉两种:

  • 饿汉:指的是在创建一个类的时候就将实例创建好!比较急!
  • 懒汉:指的是在需要用到实例的时候再去创建实例!比较懒!

因为我们单例模式只能有一个实例,那如何去保证一个实例呢?我们会马上想到类中用 static 修饰的类属性,它只有一份!保证了单例模式的基本条件!

情况一:饿汉

class Singleton{private static Singleton instance = new Singleton();private Singleton(){}public static Singleton getInstance() {return instance;}
}

我们可以看到这里饿汉模式,当多个线程并发时,并不会出现线程不安全问题,因为这里的设计模式只是针对了读操作!而单例模式的更改操作,需要看懒汉模式!

情况二:懒汉

实际情况是,只有当首次调用单例时在进行创建:

class Singleton1{private static Singleton1 instance = null;private Singleton1(){}public static Singleton1 getInstance() {if(instance==null){//①:需要时再创建实例!instance = new Singleton1();//②}return instance;}
}

❗ 存在的问题:可能创建多个实例!
假设两个线程 A 和 B 同时调用 getInstance()

  • 两者都执行到 ①,发现 instance == null → 都进入 if 块。
  • 两者都执行 ② → 各自 new 了一个对象!
  • ❌ 违反了“单例”的核心原则:全局唯一实例。

情况三:简单加锁

针对上面的情况,一个很直观的思路是:给读写操作进行加锁。所有线程在进入这个 synchronized (Singleton.class) 代码块前,必须先获取这个 Class 对象的内置锁(monitor)。同一时刻,只有一个线程能持有这个锁,其他线程必须等待。

class Singleton2{private static Singleton2 instance = null;private Singleton2(){}public static Singleton2 getInstance() {synchronized (Singleton.class){ //对读写操作进行加锁!if(instance==null){//需要时再创建实例!instance = new Singleton2();}return instance;}}
}

等价于

public static synchronized Singleton2 getInstance() {if(instance==null){//需要时再创建实例!instance = new Singleton2();}return instance;
}

❗ 仍然存在的问题:所有调用都要加锁!

  • 即使 instance 已经初始化完成,每次读取调用 getInstance() 仍要竞争锁。
  • 单例对象创建后是“只读”的,后续读取完全没必要加锁!
  • ❌ 高并发场景下成为性能瓶颈。

情况四:双重检查锁

针对上面问题,引入双重检查锁:双重检查锁(DCL)的精髓在于:

  • 第一次检查(无锁) → 如果对象已创建,直接返回,完全不加锁,性能极高!
  • 只有在对象未创建时,才加锁 + 第二次检查。
class Singleton2{private static Singleton2 instance = null;private Singleton2(){}public static Singleton2 getInstance() {if(instance==null){//如果未初始化就进行加锁操作!synchronized (Singleton.class){ //对读写操作进行加锁!if(instance==null){//需要时再创建实例!instance = new Singleton2();}}}return instance;}
}

❗ 但是!这段代码仍然有问题:
⚠️ 核心问题:JVM 的指令重排序导致其他线程可能拿到未初始化的对象!
如前所述,instance = new Singleton2(); 在底层分为三步:

  1. 分配内存
  2. 初始化对象(构造方法)
  3. instance 引用指向内存地址

JVM 可能重排序为:1 → 3 → 2
👉 线程 A 执行到第 3 步(instance != null),但对象还没初始化(第 2 步没执行)
👉 线程 B 跳过第一个 if(因为 instance != null),直接 return instance
👉 线程 B 拿到的是半初始化对象 → 调用方法或访问字段时可能崩溃!

问题:第二if是否可以删掉?

答:不可以,我们发现当有多个线程进行了第一个 if 判断后,进入的线程中有一个线程锁竞争拿到了锁,而其他线程就在这阻塞等待,直到该锁释放后,又有线程拿到了该锁,如果没有 if 判断,新拿到锁的线程又会执行实例创建代码,这样也就多次创建了实例,显然不可!!!

情况五:终极解决方案

加上 volatile 关键字, volatile 的作用:

  • 禁止指令重排序 → 保证“初始化完成”再赋值给 instance。
  • 保证可见性 → 一个线程修改 instance,其他线程立即可见。
class Singleton2{private static volatile Singleton2 instance = null;private Singleton2(){}public static Singleton2 getInstance() {if(instance==null){//如果未初始化就进行加锁操作!synchronized (Singleton.class){ //对读写操作进行加锁!if(instance==null){instance = new Singleton2();}}}return instance;}
}

文章转载自:

http://Y7pZLKQ5.jpmcb.cn
http://cqbUqzRy.jpmcb.cn
http://4RUlPdU8.jpmcb.cn
http://LmJ0mKoi.jpmcb.cn
http://R8eDrvL9.jpmcb.cn
http://Al667Nuu.jpmcb.cn
http://I1ooOfFZ.jpmcb.cn
http://onlLLZRD.jpmcb.cn
http://RHC2rixf.jpmcb.cn
http://6kuLGAVt.jpmcb.cn
http://902Xa6mG.jpmcb.cn
http://fQ9QC9hQ.jpmcb.cn
http://9zx3t1u9.jpmcb.cn
http://Py9yKRm9.jpmcb.cn
http://qRpEqb2n.jpmcb.cn
http://dM1TSlMr.jpmcb.cn
http://oxTECvZy.jpmcb.cn
http://rgndnpOF.jpmcb.cn
http://p1u1O0Cs.jpmcb.cn
http://dzZuA1y8.jpmcb.cn
http://9aeFddg0.jpmcb.cn
http://N2ZAVm2y.jpmcb.cn
http://FN72hnfA.jpmcb.cn
http://a61CH6yW.jpmcb.cn
http://HZdDU5ej.jpmcb.cn
http://EBk5GvJI.jpmcb.cn
http://jt7vun6e.jpmcb.cn
http://DNnJ5EaW.jpmcb.cn
http://j5Mn01Ea.jpmcb.cn
http://jlxXJq1f.jpmcb.cn
http://www.dtcms.com/a/384736.html

相关文章:

  • 【第五章:计算机视觉-项目实战之图像分类实战】1.经典卷积神经网络模型Backbone与图像-(4)经典卷积神经网络ResNet的架构讲解
  • 区块链:搭建简单以太坊Geth私有链
  • 数据分析:函数
  • 《投资-57》元宇宙的价值
  • Linux任务调度全攻略
  • 基于springboot的毕业旅游一站式定制系统
  • 创建其他服务器账号
  • 前端-详解ref和$refs
  • C++---变量的多维分类
  • Vue 3 前端工程化规范
  • NLP Subword 之 WordPiece 算法原理
  • 【SQL】MySQL中空值处理COALESCE函数
  • Kafka实时数据管道:ETL在流式处理中的应用
  • VBA数据结构深度解析:字典对象与集合对象的性能终极对决
  • 查看当前虚拟环境中安装的 PyTorch 版本
  • 布尔运算-区间dp
  • WWW‘25一通读 |图Anomaly/OOD检测相关文章(1)
  • 视频分类 pytorchvideo
  • RabbitMQ 基础概念与原理
  • 专题:2025中国消费市场趋势与数字化转型研究报告|附360+份报告PDF、数据仪表盘汇总下载
  • 预制菜行业新风向:企业运营与商家协同发展的实践启示
  • 晶台光耦 KL6N137 :以精密光电技术驱动智能开关性能提升
  • 贪心算法应用:最短作业优先(SJF)调度问题详解
  • javaee初阶 文件IO
  • 如何调整滚珠丝杆的反向间隙?
  • Python项目中的包添加后为什么要进行可编辑安装?
  • daily notes[45]
  • 基于51单片机的蓝牙体温计app设计
  • Git版本控制完全指南
  • 【CSS】一个自适应大小的父元素,如何让子元素的宽高比一直是2:1