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

【多线程初阶】多线程案例之单例模式

文章目录

  • 前言
  • 1. 什么是单例模式
  • 2. 饿汉模式
  • 3. 懒汉模式 --- 单线程版
  • 4. 懒汉模式 --- 多线程版
  • 5. 懒汉模式 --- 多线程改进版
  • 总结


前言

本文主要给大家讲解多线程的一个重要案例 — 单例模式.

关注收藏, 开始学习吧🧐


1. 什么是单例模式

单例模式是一种很经典的设计模式, 那么什么叫做设计模式呢?

设计模式好比象棋中的 “棋谱”.
红方当头炮, 黑方马来跳. 针对红方的一些走法, 黑方应招的时候有一些固定的套路. 按照套路来走局势就不会吃亏.
软件开发中也有很多常见的 “问题场景”. 针对这些问题场景, 大佬们总结出了一些固定的套路. 按照这个套路来实现代码, 也不会吃亏.

单例模式能保证某个类在程序中只存在唯一一份实例, 而不会创建出多个实例.

这一点在很多场景上都需要. 比如 JDBC 中的 DataSource 实例就只需要一个.

单例模式具体的实现方式有很多种写法, 在这里我们主要讲解 “饿汉”“懒汉” 两种.

2. 饿汉模式

核心思想: 类加载的同时, 创建实例.

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

注意:

  1. private static Singleton instanse = new Singleton(); 被 static 修饰, 该属性是类的属性, JVM 中, 每个类的类对象只有唯一一份, 类对象里的这个成员自然也是唯一一份了.
  2. private Singleton() {}; 将构造方法设为 private, 就可以将外部的 new 操作给禁用掉.
  3. 此时, 在类内部把实例创建好, 同时禁止外部重新创造实例, 就可以保证单例的特性了.

在这里插入图片描述
由于构造方法设为 private, 导致 new 操作被禁用, 我们只能通过类方法 .getInstanse 来创建实例, 可以看到, 这样先后创建的 s1 和 s2 实例, 其实是同一个实例.

3. 懒汉模式 — 单线程版

核心思想: 类加载的时候不创建实例. 第一次使用的时候才创建实例.

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

注意点与饿汉模式差不多, 但是区别在于, 懒汉模式是 “非必要不创建”, 可以看到, instance 实例对象是在调用类方法时才创建的.

4. 懒汉模式 — 多线程版

现在问题来了, 上述两个模式, 是否能构保证线程安全呢?

多个线程下调用 getInstance 方法, 是否会出现问题呢?

回想一下我们之前讲解的线程不安全的几个原因. 可以推断饿汉模式下, 线程是安全的, 因为他只是读数据, 并没有进行写数据.

但是多线程下, 懒汉模式可能无法保证创建对象的唯一性, 线程不安全.

线程安全问题发生在首次创建实例时. 如果在多个线程中同时调用 getInstance 方法, 就可能导致创建出多个实例.
一旦实例已经创建好了, 后面再多线程环境调用 getInstance 就不再有线程安全问题了(不再修改instance 了)

我们可以通过加锁, 利用 synchronized 关键字就可以改善这里的线程安全问题.

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

5. 懒汉模式 — 多线程改进版

加锁其实是一个比较低效的操作, 因为他会造成阻塞等待, 非必要还是不要进行加锁.

以下代码在加锁的基础上, 做出了进一步改动:

  • 使用双重 if 判定, 降低锁竞争的频率.
  • 给 instance 加上了 volatile.
class SingletonLazy {private static volatile SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {};
}

理解双重 if 判定 / volatile:

加锁 / 解锁是一件开销比较高的事情. 而懒汉模式的线程不安全只是发生在首次创建实例的时候. 因此后续使用的时候, 不必再进行加锁了.

  • 外层的 if 就是判定下看当前是否已经把 instance 实例创建出来了.
  • 同时为了避免 “内存可见性” 导致读取的 instance 出现偏差, 于是补充上 volatile .
  • 当多线程首次调用 getInstance, 大家可能都发现 instance 为 null, 于是又继续往下执行来竞争锁, 其中竞争成功的线程, 再完成创建实例的操作.
  • 当这个实例创建完了之后, 其他竞争到锁的线程就被里层 if 挡住了. 也就不会继续创建其他实例.
  • 有三个线程, 开始执行 getInstance , 通过外层的 if (instance == null) 知道了实例还没有创建的消息. 于是开始竞争同一把锁.

总结

✨ 本文讲解了线程安全下的单例模式, 由于饿汉模式只是读操作, 天生就是安全的, 而懒汉模式不是安全的, 因为有写操作, 我们通过加锁, 并利用双重 if 来减少不必要的加锁操作, 再使用 volatile 禁止指令重排序, 使其变得安全.
✨ 想了解更多的多线程知识, 可以收藏一下本人的多线程学习专栏, 里面会持续更新本人的学习记录, 跟随我一起不断学习.
✨ 感谢你们的耐心阅读, 博主本人也是一名学生, 也还有需要很多学习的东西. 写这篇文章是以本人所学内容为基础, 日后也会不断更新自己的学习记录, 我们一起努力进步, 变得优秀, 小小菜鸟, 也能有大大梦想, 关注我, 一起学习.

再次感谢你们的阅读, 你们的鼓励是我创作的最大动力!!!!!

http://www.lryc.cn/news/108605.html

相关文章:

  • 跨境选品怎么选?建议独立站卖家收下这份利基产品查找攻略!
  • [C++项目] Boost文档 站内搜索引擎(1): 项目背景介绍、相关技术栈、相关概念介绍...
  • opencv-32 图像平滑处理-高斯滤波cv2.GaussianBlur()
  • Windows 环境Kubernetes安装
  • 自建类ChatGPT服务:本地化部署与远程访问教程
  • 常用SQL语句总结
  • arm交叉编译lmbench
  • ExtJs 7.7.0 下载方法与去除trial水印
  • Android11开发规划
  • 活动隔断在现在酒店运用的方式
  • Jenkins工具系列 —— 插件 钉钉发送消息
  • LeetCode 26 题:删除有序数组的重复项
  • 优雅地切换node版本(windows)
  • 反诈:吴明军、黄亮领导的WIN生活资金盘,大家警惕防范此类诈骗
  • shell、bash的关系及bash的特性(一)
  • 【问题随记】
  • Stable Diffusion AI绘画学习指南【常用模型,采样器介绍】
  • pycharm——漏斗图
  • RISC-V基础之浮点指令(包含实例)
  • 前端生成图片验证码怎么做?
  • 【Java】springboot框架 粮油质量溯源MES生产加工管理系统源码
  • macOS install redis遇到的bug(tar包,homebrew安装,守护进程redis.conf配置)
  • 面试题:创建JS对象的几种方式?构造函数是什么?new操作符具体干了什么?为什么字符串可以使用length?
  • LabVIEW深度相机与三维定位实战(下)
  • 【基础类】—CSS盒模型的全面认识
  • ATFX汇评:非农就业报告来袭,汇市或迎剧烈波动
  • SpringBoot的常用注解的服用方式
  • [课程][原创]CMakeLists编写实战linux版
  • 静态路由下一跳地址怎么确定(静态路由配置及讲解)
  • SPSS‖参数与非参数检验对比课程中的 配对样本T检验