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

【Java笔记】单例模式

目录

  • 1. 饿汉模式
  • 2. 懒汉模式
    • 2.1 线程不安全
    • 2.2 线程不安全原因分析
      • 2.2.1 原因
      • 2.2.2 给 new SingletonLazy() 加锁
      • 2.2.3 在外层加锁
      • 2.2.4 存在的问题
  • 3. 双重检查锁(Double Check Lock,DCL)(重要)
    • 3.1 DCL解释
    • 3.2 重要补充:volatile关键字的作用

单例模式是保证一个类在整个应用程序中只有一个实例,同时提供一个统一的全局访问入口,避免因频繁创建对象造成内存浪费或状态不一致的问题。

1. 饿汉模式

代码:

public class SingletonHungry {// 定义成员变量,使用static修饰保证全局唯一private static SingletonHungry instance = new SingletonHungry();// 构造方法私有化, 禁止外部实例化对象private SingletonHungry() {}// 加 static 将方法编程静态代码块,属于类,通过 类名.方法名 的方式调用public static SingletonHungry getInstance() {return instance;}
}

把这种类加载的时候就完成对象初始化的创建方式称为 “饿汉模式”
调用代码:

    public static void main(String[] args) {SingletonHungry instance1 = SingletonHungry.getInstance();System.out.println(instance1);SingletonHungry instance2 = SingletonHungry.getInstance();System.out.println(instance2);SingletonHungry instance3 = SingletonHungry.getInstance();System.out.println(instance3);}

在这里插入图片描述
获取到的都是同一个对象

2. 懒汉模式

2.1 线程不安全

代码:

public class SingletonLazy {// 不初始化private static SingletonLazy instance;private SingletonLazy() {}// 对外提供一个获取对象的方法public static SingletonLazy getInstance() {if (instance == null) {instance = new SingletonLazy();}return instance;}
}

这种方法在单线程中得到的对象都是同一个,但是在多线程环境下会有线程安全问题!
创建10个线程,调用 SingletonLazy :

public static void main(String[] args) {for (int i = 0; i < 10; i++) {Thread thread = new Thread(() -> {SingletonLazy instance = SingletonLazy.getInstance();System.out.println(instance);});thread.start();}
}

执行结果:
在这里插入图片描述
得到的对象不是同一个,存在线程安全问题。

2.2 线程不安全原因分析

2.2.1 原因

在这里插入图片描述
有多少个线程判断了 instance == null,就会 new 多少个对象!

2.2.2 给 new SingletonLazy() 加锁

public static SingletonLazy getInstance() {// 第一次判断是否需要加锁if (instance == null) {synchronized (SingletonLazy.class) {instance = new SingletonLazy();}}return instance;
}

此时的代码依旧是线程不安全的!!!
在这里插入图片描述

原因:
在这里插入图片描述
有多少个线程进入了 if 代码块,就会有多少个对象被实例化!

2.2.3 在外层加锁

代码:

public static SingletonLazy getInstance() {// 第一次判断是否需要加锁synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}return instance;}

不会出现线程安全问题了。
有两个线程 t1 和 t2 ,t1 先拿到锁资源,只有线程 t1 全部执行完释放锁之后 t2 才有可能拿到锁资源,但此时 对象已经不为 null 了,就不会进入 if 代码块,就不会再次创建对象。

2.2.4 存在的问题

  1. 当第一个线程进入这个方法时,如果变量没有初始化,则获取锁进行初始化操作,此时单例对象被第一个线程创建完成;
  2. 后面的线程以后也永远不会再执行new对象的操作;
  3. synchronized还有没有必要加了?
    当第一个线程把对象创建好之后,就没有必要了,从第二个线程开始这个加锁解锁都是无效的操作,synchronized 关键字对应了CPU中的指令,LOCK 和 UNLOCK 对应的锁指令是互斥锁,比较消耗系统资源。

解决办法:在加锁前再次判断一下是否需要加锁

3. 双重检查锁(Double Check Lock,DCL)(重要)

3.1 DCL解释

代码:

public class SingletonDCL {private static volatile SingletonDCL instance;private SingletonDCL() {}// 对外提供一个获取对象的方法public static SingletonDCL getInstance() {// 第一次判断是否需要加锁if (instance == null) {synchronized (SingletonDCL.class) {if (instance == null) {instance = new SingletonDCL();}}}return instance;}
}

解析:

  1. 有 t1 和 t2 两个线程,假设t1、t2 同时进入了 if 代码块并判断 instance == null, t1 先拿到了锁资源,再次判断instance 为 null,则创建了一个对象,此时 instance 不为空了,释放锁资源后返回了这个 instance;
  2. t2 拿到锁资源,此时 instance 不为空了,则不会进入第二个 if 代码块,直接释放锁资源返回已经创建好了的对象;
  3. 当有其他线程再次获取对象时,instance 不为空,则不会进入第一层 if 代码块,直接返回已经创建好了的对象,保证了单例。

3.2 重要补充:volatile关键字的作用

private static volatile SingletonDCL instance;

只要在多线程环境中修改了共享变量就要加 volatile ,主要是考虑到指令重排序的问题

new 一个对象的步骤:

  1. 在内存中申请一片空间
  2. 初始化对象的属性(赋初值)
  3. 把对象在内存中的首地址赋值给对象的引用

1 和 3 是强相关的,只有在分配完内存空间之后才会执行 3,但2并不是强相关的,可能会发生指令重排序
正常执行顺序:1、2、3
可能的重排序后顺序:1、3、2
重排序之后,在分配完内存空间后直接把对象在内存中的首地址赋值给对象的引用,此时的 instance 是一个尚未初始化完成的对象,其他线程如果访问这个未初始化完成的对象,就会导致出现错误!因此要加 volatile 禁止指令重排序!
同时也保证了可见性​,确保一个线程修改了 instance 的值后,其他线程能立即看到最新值。


文章转载自:

http://8Qad0PaE.pxbky.cn
http://NwVtxSd4.pxbky.cn
http://X8PG8Zvn.pxbky.cn
http://AK5uEqEq.pxbky.cn
http://m4D7GU7y.pxbky.cn
http://qjas8e0r.pxbky.cn
http://DwtutVkp.pxbky.cn
http://QqLZjcpI.pxbky.cn
http://D7fGXeQU.pxbky.cn
http://unMDtOSl.pxbky.cn
http://CnT29sSv.pxbky.cn
http://fowXSBWy.pxbky.cn
http://GwyHK8ZC.pxbky.cn
http://OG2oQQaA.pxbky.cn
http://xgsK9PZg.pxbky.cn
http://PpirAYoz.pxbky.cn
http://cEERueU9.pxbky.cn
http://cDzD6QhZ.pxbky.cn
http://aMZTZuav.pxbky.cn
http://MFNrdP3L.pxbky.cn
http://kdFBIRWs.pxbky.cn
http://Q98SeDcL.pxbky.cn
http://8ADfBkp4.pxbky.cn
http://NpkqKSeg.pxbky.cn
http://9fZQz5sq.pxbky.cn
http://TIgp7XCp.pxbky.cn
http://GAZCGnyR.pxbky.cn
http://R5lXGtHl.pxbky.cn
http://pe38nlqp.pxbky.cn
http://8ez6IB9W.pxbky.cn
http://www.dtcms.com/a/372042.html

相关文章:

  • 腕部骨折X光检测识别数据集:2w+图像,6类,yolo标注
  • 当没办法实现从win复制东西到Linux虚拟机时的解决办法
  • AI话术—知识库多次返回播放不同的内容(智能呼叫系统)
  • 【系统架构设计(20)】构件与中间件技术
  • 使用Terraform管理阿里云基础设施
  • 【01】针对开源收银系统icepos (宝塔面板) 详细安装教程详细参考-优雅草卓伊凡
  • python中的“与或非“与vue中的“与或非“
  • c6-类和对象-对象特征-类对象做对象成员
  • 云服务扫盲笔记(2) —— SLS 接入与设置自动化
  • 【算法--链表】109.有序链表转换二叉搜索树--通俗讲解
  • Java 网络编程学习笔记
  • kerberos详解
  • 【数据结构基础习题】-1- 数据结构基本操作
  • OSCP - Proving Grounds - Catto
  • Claude Code 使用指南
  • RabbitMQ 持久化
  • matrix-breakout-2-morpheus靶机渗透
  • 学习结构体
  • Docker 容器 OOM:从资源监控到JVM调优的实战记录
  • TypeORM、Sequelize、Hibernate 的优缺点对比:新手常见 SQL 与 ORM 踩坑总结
  • 企业级低代码平台的条件函数系统设计:从复杂到极简的架构演进
  • ICCV-2025 | 中科院自动化所世界模型助力具身导航!NavMorph:连续环境中的视觉语言导航自演化世界模型
  • ChatGPT 协作排查:Node.js 内存泄漏的定位与修复
  • Cannot resolve plugin org.apache.maven.plugins:maven-site-plugin:3.1.0
  • 备战 2025 软考系统架构师
  • RabbitMQ 重试机制 和 TTL
  • 人工智能竞赛提高mAP的方法
  • 深度学习——残差神经网络案例
  • LeetCode 刷题【68. 文本左右对齐】
  • Day23_【机器学习—集成学习(5)—Boosting—XGBoost算法】