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

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

/** * 懒汉式 方式6 双重检查锁 */publicclassSingletonLazy{// 1、私有构造方法privateSingletonLazy(){}// 2、在成员变量位置声明一个静态对象privatestaticvolatileSingletonLazyinstance;// 关键字volatile:禁止指令重排序,确保绝对安全// 3、对外提供静态方法获取唯一实例publicstaticSingletonLazygetInstance(){if(instance==null){// 提升性能:第一次判断,若instance不为空,不进入抢锁阶段,直接返回实例synchronized(SingletonLazy.class){if(instance==null){// 确保安全:第二次判断,抢到锁后再次判断是否为nullinstance=newSingletonLazy();}}}returninstance;}}
  1. synchronized (SingletonLazy.class) 是干什么的?
    作用: 这是一个互斥锁(排他锁)。
    因为 getInstance 是一个 static 方法,它不属于任何对象实例,而是属于类本身。所以需要锁住 SingletonLazy.class 这个类对象。

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

不管外面有多少人(线程)想进去,同一时间只有一个人能拿着钥匙进去。
其他人如果没有抢到钥匙,就必须在门口排队等待(Block),直到里面的人出来并归还钥匙。
2. 为什么抢到锁了,还要做第二次判断?
**疑问:“既然线程 A 进去了,线程 B 就进不去,那线程 A 放心大胆地创建不就行了吗?”

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

模拟一个多线程并发的场景就明白了:
场景还原:
假设有两个线程:线程 A 和 线程 B,同时调用 getInstance(),且此时 instance 为 null。

第一步(关键):
线程 A 执行到第 11 行 if (instance == null),判断为 true(需要创建)。
同时,线程 B 也执行到第 11 行,因为它还没被锁住,它看到的 instance 也是 null,判断也为 true。
结果: A 和 B 都通过了第一层关卡,来到了 synchronized 门外。
第二步(抢锁):
线程 A 动作快,抢到了锁,进入了 synchronized 块内部。
线程 B 慢了一步,被挡在门外,进入阻塞(等待)状态。
注意: 此时线程 B 已经通过了第一个 if,它只是在等锁,而不是被第一个 if 挡回去。
第三步(A 创建对象):
线程 A 在锁内部,执行 instance = new SingletonLazy();。
对象创建完毕,instance 不再是 null。
线程 A 释放锁,离开。
第四步(B 的灾难):
锁被释放了,线程 B 终于拿到了锁,进入了 synchronized 块内部。
如果没有第二次判断: 线程 B 会直接执行 new SingletonLazy()。
后果: 虽然 A 已经创建了一个对象,但 B 又创建了一个!单例模式失效了,变成了“双例”。
第五步(B 的救赎 - 第二次判断):
因为有了第 13 行的 if (instance == null)。
线程 B 进门后,虽然它之前在门外看到的是 null,但它进门后再看一眼:发现 instance 已经被 A 初始化了(不为 null)。
结果: 线程 B 放弃创建,直接退出。
总结
第一次判断 (if outside): 是为了性能。一旦对象创建好了,后面的线程根本不需要排队抢锁,直接拿走对象即可。
第二次判断 (if inside): 是为了安全。防止在多线程并发的极端情况下,两个线程同时通过了第一层检查,导致重复创建对象。
补充
为什么instance 变量必须加上 volatile 关键字?。
在 Java 高并发环境下,这段代码不加volatile关键字有一个隐患。

// 必须加 volatileprivatestaticvolatileSingletonLazyinstance;

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

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

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

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

相关文章:

  • 必收藏!小白程序员入门LLM:从应用到原理,掌控AI不被反制
  • Taskrunner:Arduino裸机实时任务调度器深度解析
  • 镜像视界 · 公安实战场景空间智能底座与目标连续控制体系白皮书——以 Pixel2Geo™ 像素空间反演引擎为核心,融合 MatrixFusion™ 矩阵视频融合与 NeuroRebuild™ 动态
  • 遇到GPU驱动冲突问题,云厂商通常提供怎样的技术支持?
  • STM32智能展柜控制系统设计与实现
  • 推挽电路原理与应用全解析
  • 为什么选择专业人力资源公司进行薪酬核算?5大优势助力企业高效合规
  • PDE (Processing D Editor) 三维场景编辑器 · 软件白皮书 · 基于 v..
  • 94吨黄金“上链搬家”,手续费仅0.0016%!黄金RWA正在改写跨境资产流动
  • 第三节:Tool 的一生 —— 从定义到执行的完整生命周期
  • 爱站网SEO工具包的网站优化报告如何读懂_如何利用爱站网SEO工具包实现网站流量提升
  • SEO推广服务商与自建团队相比有什么优势_SEO推广服务商如何提高网站的搜索引擎友好度
  • 探索PLECS仿真下DAB变换器峰值电流前馈控制策略——IEEE顶刊复现之旅
  • Win32---->菜单和其他资源
  • ESP8266模组开发与AT指令实战指南
  • Memfit AI 渗透测试智能体,到底能不能打?
  • Linux 系列从多节点的catalina 日志中统计设备调用频次
  • Arduino I2C LCD库深度解析:printf支持与HD44780驱动优化
  • RNN,LSTM,BiLSTM算法的具体细节
  • OpenClaw调试技巧:千问3.5-27B任务失败的根本原因分析
  • STM32电位器驱动库:轻量级ADC封装与中值滤波实现
  • 海口上门做饭哪个靠谱
  • 森利威尔SL3073替代RT2862 4-65V超宽压3A降压芯片
  • 基于Matlab的多自由度轴承静刚度计算之旅
  • 【网络安全】入侵检测系统IDS
  • Vodafone K4606 USB调制解调器Linux内核驱动适配
  • 解决网易云音乐NCM格式限制的ncmdump:技术原理与高效解密实践指南
  • LABVIEW写入Excel的函数:应用程序目录、创建路径、写入带分隔符电子表格、for循环、条件结构、按名称解除捆绑、创建数组
  • 企微第三方应用开发避坑指南:从回调服务到内网穿透的实战经验
  • 5分钟用OpenClaw连接SecGPT-14B:网络安全自动化初体验