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

一文带你了解单例模式及其逐步优化~

单例模式

单例模式是一种创建型设计模式,它确保一个类只有一个实例,并提供一个全局访问点来获取该实例。

使用场景:

  1. 需要频繁创建和销毁的对象

  2. 创建对象时耗时过多或资源消耗过大

  3. 工具类对象(无状态的工具类)

  4. 访问数据库或文件的对象(如数据源、session工厂)

  5. 系统级资源(如任务管理器、回收站)

常用的两种实现模式分为饿汉模式和懒汉模式,他们两者的区别在于创建时机。

饿汉模式能够在编译阶段创建实例,懒汉模式会在使用时才会创建实例。

饿汉模式

饿在这里面时急迫的意思,在这里面就是尽早的去创建实例。

在实现的时候我们通过static来修饰实例,来确保他可以在编译阶段创建出实例。

为了避免外界可以随意创建改实例,我们还需要对他的初始化方法使用private进行修饰,此处是我们实现单例的关键所在。

最后我们使用getInstace的方法返回这一个实例。

以下是参考代码

class singletonHungry {//因为是static的所以他在编译开始的时候就会创建private static singletonHungry instance = new singletonHungry();//只能通过get方法访问唯一的这个实例public static singletonHungry getInstance() {return instance;//只有读操作}//通过使用private方法使外界无法创建。private singletonHungry() {}
}

因为此操作只涉及读操作,因此并不涉及线程安全问题。

懒汉模式

懒和饿是相对的,他会尽可能晚的创建实例,懒在计算机中并不是一个贬义词,尽晚的使用反而会减少实例对计算机的负荷。

懒汉模式的实现和饿汉是相似的,初始化方法也使用private进行修饰。

不同的是他的创建是在getinstance的时候进行的。

以下是参考代码

class singletonLazy {private static singletonLazy instance = null;public singletonLazy getInstance() {//当用到他的时候在创建if (instance == null) {instance = new singletonLazy();}return instance;}private singletonLazy() {}
}

他在getInstace的时候涉及读和写两种操作,在多线程下可能会产生bug,因此他是线程不安全的。

线程安全的考虑及其优化

线程安全问题

在懒汉模式中,虽然赋值是原子性的操作,但是加上if整体上就不是了,因此我们需要对其进行加锁操作。

class singletonLazy {private static singletonLazy instance = null;public synchronized singletonLazy getInstance() {//加锁//当用到他的时候在创建if (instance == null) {instance = new singletonLazy();//赋值是原子性的,但是加上if就不是了}return instance;}private singletonLazy() {}
}

执行效率优化

但是此时我们就会出现新的问题,加锁也是有代价的。

它仅仅是在线程未创建的时候会涉及到读和写操作,其他情况只涉及读操作,并不涉及线程安全问题。

虽然在这个时候我们保证了线程安全,但是因为锁的存在,他会相互阻塞,影响了执行效率。

因此我们可以这样优化:

class singletonLazy {private static singletonLazy instance = null;public singletonLazy getInstance() {if (instance == null) {synchronized (locker) {if (instance == null) {instance = new singletonLazy();}}}return instance;}private singletonLazy() {}
}

通过这么一个巧妙的写法,我们就可以解决上述的问题~~

内存可见性问题

但是我们的优化还没有结束,他是否会涉及内存可见性问题呢?编译器优化的问题我们无法预测,因此为了稳妥起见,我们可以给Instace直接加一个volatile,从根本上杜绝内存可见性问题。

另外,volatile不仅保证了内存可见性问题,还保证了指令重排序的问题。

什么是指令重排序问题呢?

指令重排序:也是编译器优化的一种形式,调整代码运行的先后顺序,以得到提高性能的效果。指令重排序的大前提是逻辑不变,在多线程的环境下,这里的判定可能出现失误。

在上述的优化代码中可能会出现这样的情况:

正常顺序:申请空间->开辟空间->赋值引用多线程下可能出现如下:
线程1			线程2
申请空间|
赋值引用|此时线程2执行发现instance不为空了return instance;
开辟空间此时线程2拿到的引用是一个还未开辟空间的地址

你或许会产生疑惑,我们不是加锁了吗?为什么还会有多线程的问题。

这个问题源于我们的优化导致的,我们在上述使用了双重if,而我们的锁是在第二个if里面的,因此第一个if是不受锁的影响,导致了其他线程的可乘之机。

但是这个问题终究是源于指令重排序,因此我们只需要加上volatile就可以完美解决了~

class singletonLazy {private static volatile singletonLazy instance = null;public singletonLazy getInstance() {if (instance == null) {synchronized (locker) {if (instance == null) {instance = new singletonLazy();}}}return instance;}private singletonLazy() {}
}
http://www.xdnf.cn/news/132895.html

相关文章:

  • 【差分隐私】假设检验的视角(高斯差分隐私)
  • 07 Python 字符串全解析
  • 基于LAB颜色空间的增强型颜色迁移算法
  • [Mybatis-plus]
  • IEEE期刊目录重磅更新!共242本期刊被收录!
  • ubuntu22.04部署Snipe-IT
  • C++初登门槛
  • Unreal制作角色冲锋时冲击波效果
  • markdown自动标题序号,标题序号,目录处理
  • 待办事项日历组件实现
  • ViT论文及代码解读
  • synchronization
  • 八大排序——冒泡排序/归并排序
  • C++经典知识网页保存
  • 前端开发实用技巧:封装通用下载导出文件或图片方法
  • 2025年深度学习模型发展全景透视(基于前沿技术突破与开源生态演进的交叉分析)
  • 39个常用的AI指令,笔尖Ai写作、DeepSeek、腾讯元宝、豆包、Kimi等都能用
  • 制作一个简单的操作系统10
  • Android开发,实现底部弹出菜单
  • GStreamer 简明教程(十一):插件开发,以一个音频生成(Audio Source)插件为例
  • ‌Linux trap 命令详解
  • report builder问题
  • springboot3 声明式 HTTP 接口
  • JUC多线程:读写锁
  • 【高频考点精讲】前端构建工具对比:Webpack、Vite、Rollup和Parcel
  • 淘宝 /天猫/1688|京东API 常用接口列表与申请方式解析
  • P12167 [蓝桥杯 2025 省 C/Python A] 倒水
  • 对接金蝶获取接口授权代码
  • 第3讲、大模型如何理解和表示单词:词嵌入向量原理详解
  • Blender好用的插件推荐汇总