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

单例模式 懒汉式(双重检查锁)

/*** 懒汉式 方式6 双重检查锁*/
public class SingletonLazy {// 1、私有构造方法private SingletonLazy(){}// 2、在成员变量位置声明一个静态对象private static volatile SingletonLazy instance; // 关键字volatile:禁止指令重排序,确保绝对安全// 3、对外提供静态方法获取唯一实例public static SingletonLazy getInstance() {if (instance == null) { // 提升性能:第一次判断,若instance不为空,不进入抢锁阶段,直接返回实例synchronized (SingletonLazy.class) {if (instance == null) { // 确保安全:第二次判断,抢到锁后再次判断是否为nullinstance = new SingletonLazy();}}}return instance;}}

1. synchronized (SingletonLazy.class) 是干什么的?

作用: 这是一个互斥锁(排他锁)
因为 getInstance 是一个 static 方法,它不属于任何对象实例,而是属于类本身。所以需要锁住 SingletonLazy.class 这个类对象。

比喻:
想象 synchronized 花括号 { ... } 包裹的代码块是一个试衣间,而 SingletonLazy.class 是这个试衣间唯一的钥匙

  • 不管外面有多少人(线程)想进去,同一时间只有一个人能拿着钥匙进去
  • 其他人如果没有抢到钥匙,就必须在门口排队等待(Block),直到里面的人出来并归还钥匙。

2. 为什么抢到锁了,还要做第二次判断?

**疑问:“既然线程 A 进去了,线程 B 就进不去,那线程 A 放心大胆地创建不就行了吗?”

答案:为了防止“漏网之鱼”。

模拟一个多线程并发的场景就明白了:

场景还原:

假设有两个线程:线程 A线程 B,同时调用 getInstance(),且此时 instancenull

  1. 第一步(关键):
  • 线程 A 执行到第 11 行 if (instance == null),判断为 true(需要创建)。
  • 同时线程 B 也执行到第 11 行,因为它还没被锁住,它看到的 instance 也是 null,判断也为 true。
  • 结果: A 和 B 都通过了第一层关卡,来到了 synchronized 门外。
  1. 第二步(抢锁):
  • 线程 A 动作快,抢到了锁,进入了 synchronized 块内部。
  • 线程 B 慢了一步,被挡在门外,进入阻塞(等待)状态
  • 注意: 此时线程 B 已经通过了第一个 if,它只是在等锁,而不是被第一个 if 挡回去。
  1. 第三步(A 创建对象):
  • 线程 A 在锁内部,执行 instance = new SingletonLazy();
  • 对象创建完毕,instance 不再是 null。
  • 线程 A 释放锁,离开。
  1. 第四步(B 的灾难):
  • 锁被释放了,线程 B 终于拿到了锁,进入了 synchronized 块内部。
  • 如果没有第二次判断: 线程 B 会直接执行 new SingletonLazy()
  • 后果: 虽然 A 已经创建了一个对象,但 B 又创建了一个!单例模式失效了,变成了“双例”。
  1. 第五步(B 的救赎 - 第二次判断):
  • 因为有了第 13 行的 if (instance == null)
  • 线程 B 进门后,虽然它之前在门外看到的是 null,但它进门后再看一眼:发现 instance 已经被 A 初始化了(不为 null)。
  • 结果: 线程 B 放弃创建,直接退出。

总结

  • 第一次判断 (if outside): 是为了性能。一旦对象创建好了,后面的线程根本不需要排队抢锁,直接拿走对象即可。
  • 第二次判断 (if inside): 是为了安全。防止在多线程并发的极端情况下,两个线程同时通过了第一层检查,导致重复创建对象。

补充

为什么instance 变量必须加上 volatile 关键字?。
在 Java 高并发环境下,这段代码不加volatile关键字有一个隐患。

// 必须加 volatile
private static volatile SingletonLazy instance;

原因: new SingletonLazy() 这个操作在计算机底层不是原子性的,它分三步:

  1. 分配内存。
  2. 初始化对象。
  3. instance 指向这块内存。

如果没有 volatile,CPU 可能会指令重排序(比如变成 1 -> 3 -> 2)。
如果在执行完 1 和 3(此时 instance 已经不为 null,但对象还没初始化好)时,另一个线程来了,它在第一次判断时发现 instance 不为 null,直接拿去用了。结果就是:拿到了一个半成品对象,程序报错。

volatile 的作用就是禁止指令重排序,保证绝对安全。

http://www.jsqmd.com/news/314021/

相关文章:

  • 用Ticker API写一个行情面板:一次完整的实现过程
  • 2026年1月28日
  • 社会网络仿真软件:NetLogo_(8).NetLogo在社会网络建模中的应用
  • 社会网络仿真软件:NetLogo_(8).社会网络动态分析
  • 1 人公司 + 智能体军团:流量、内容、营销、变现体系
  • weixin193基于微信小程序的社区垃圾回收管理系统ssm(源码)_kaic
  • 社会网络仿真软件:NetLogo_(9).可视化技术与应用
  • weixin194高校学习助手小程序ssm(源码)_kaic
  • 【毕设】基于Python的Django-html基于web漏洞挖掘技术的研究
  • 解读欧美安全准绳:一氧化碳报警器制造商如何精准选择核心器件
  • 社会网络仿真软件:NetLogo_(4).NetLogo编程基础
  • 社会网络仿真软件:NetLogo_(5).NetLogo模型库解析
  • 10、C语言程序设计:define编译预处理在嵌入式开发中的应用
  • 【Matlab】MATLAB矩阵子矩阵索引详解:从语法案例到分块应用
  • 奇正沐古:靠谱的大健康行业品牌全案营销咨询公司
  • 使用 Python 将 PDF 转成 Excel:高效数据提取的自动化之道 - 详解
  • 2026年西安装修公司综合实力排名:透明报价/精湛工艺/业主口碑全解析
  • 得物商品详情接入的场景
  • Riemann-Geometry PINN机械退化趋势预测(Pytorch)
  • C++游戏开发之旅 6
  • 恒小花额度变现全解析:热门品类刻意隐藏,转现损失远超预期
  • 电力绝缘子缺陷检测:基于YOLOv26的智能识别系统_2
  • 击剑运动员与武器识别 _ 基于YOLOv26的实时检测系统_1
  • 计算机毕设Java基于java的停车场管理系统 基于Java技术的智能停车场管理平台设计与实现 Java驱动的停车场综合管理系统开发
  • 计算机毕设Java基于微信小程序的餐厅点餐系统的设计与实现 基于微信小程序的Java餐厅点餐系统开发与实践 Java技术驱动的微信小程序餐厅点餐系统设计
  • 航拍船舶数据集914张VOC+YOLO格式
  • 红外拍摄建筑缺陷数据集463张VOC+YOLO格式
  • 计算机毕设java和vue的学生宿舍管理系统 基于 Java 和 Vue 的学生宿舍管理系统 构建高效便捷的宿舍管理平台
  • 计算机毕设Java基于JAVA的酒水销售系统 基于Java技术的酒水销售与管理系统开发 Java驱动的酒水销售平台设计与实现
  • Day23获取表单并修改属性