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

线程P5 | 单例模式[线程安全版]~懒汉 + 饿汉

什么是单例模式?

在我们正式讲解单例模式之前,没有了解过的小伙伴可能会有疑问...到底啥叫单例模式??其实单例模式呢,是我们设计模式中的一种,所谓的设计模式,你可以把它理解为一个模板,也就是你在实现某种业务的时候,选择适配的设计模式,根据这个模板来改你对应的业务代码

Java设计模式是解决特定软件设计问题的经典、可复用的方案模板,分为创建型、结构型和行为型三大类,帮助开发者编写更灵活、可维护的代码。

那么我们的单例模式呢,指的是实例对象只会被创建一次这样的设计模式~~

为了实现这样的要求,单例模式中又有两种形式:饿汉模式懒汉模式,接下来我们将会为大家一一介绍这两种模式

饿汉模式

什么是饿汉模式

所谓的饿汉模式,其实指的是实例从代码刚开始运行的时候就已经创建好的模式,那实例就处在一个等着被调用的状态,所以就一直饿着来等待资源~~因此就叫做饿汉模式啦

饿汉模式实现

class Singleton {   //饿汉模式private static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}private Singleton() {}}
public class Demo19 {public static void main(String[] args) {Singleton t1 = Singleton.getInstance();Singleton t2 = Singleton.getInstance();boolean res = t1 == t2;System.out.println(res);}
}

如上,我们运行结果为true,因为此时它们引用的都是同一个实例~~所以是符合单例模式的

我们可以发现,单例模式的实现,即外部无法创建一个实例是通过将构造方法变为private来实现的,此时的话外部只能通过getInstance()方法来访问内部已经创建好的那个实例

线程安全问题

那么,饿汉模式有没有线程安全问题呢?我们可以发现,饿汉模式中只有读操作,所以是没有线程安全问题的~~可以放心大胆使用

不过饿汉模式还是有缺点的,因为实例一开始就被创建了,一直等着被使用,这是很浪费资源的行为~~

懒汉模式

什么是懒汉模式

所谓的懒汉模式,指的是实例只有在被使用的时候才会开始创建,因此听起来就很懒啦,所以就被叫做懒汉模式,不过这里的"懒"可是褒义词,因为这可以节约资源~~

懒汉模式实现

class SingletonLazy {private static SingletonLazy instance = null;public static SingletonLazy getInstance() {if (instance == null) {             //判断是否有实例创建了instance = new SingletonLazy();}return instance;}private SingletonLazy() {}
}
public class Demo20 {   //懒汉模式public static void main(String[] args) {SingletonLazy t1 = SingletonLazy.getInstance();SingletonLazy t2 = SingletonLazy.getInstance();System.out.println(t1 == t2);}
}

上述我们给出了一个按照描述的懒汉模式,实例一开始是null,只有当被调用的时候才会根据判断实例是否创建过来创建实例

线程安全问题

当我们细看上述代码的时候,其实可以发现,对于instance对象,我们既有读操作,又有写操作,所以事实上,这个代码是存在线程安全问题的

当t1和t2同时调用的时候,有可能会创建两个实例,这就不符合单例模式的要求了;

我们细看可以发现,此时创建实例这个操作并不是原子的,是if + 创建一起的,这也是会引发线程安全问题的原因

因此,为了解决上述问题,我们可以把这个操作加个锁,把它们变成原子的~~

解决原子性问题代码
class SingletonLazy {private static SingletonLazy instance = null;private static Object locker = new Object();public static SingletonLazy getInstance() {synchronized (locker) {                 //使得if和创建实例变成一个整体的原子操作,防止多个线程同时操作时创建多个实例if (instance == null) {             //判断是否有实例创建了instance = new SingletonLazy();}}return instance;}private SingletonLazy() {}
}
public class Demo20 {   //懒汉模式public static void main(String[] args) {SingletonLazy t1 = SingletonLazy.getInstance();SingletonLazy t2 = SingletonLazy.getInstance();System.out.println(t1 == t2);}
}

OK~~我们的原子性问题已经解决啦(●'◡'●)

但是...我们再来看下这个代码,线程安全问题有没有解决完呢?回顾我们P4中提到的引起线程安全问题的原因,此时其实我们是两个线程在对一个对象进行操作,所以是很可能发生指令重排序和内存可见性问题的,因此我们应该给这个对象加上volatile~~

解决指令重排序和内存可见性问题
class SingletonLazy {private static volatile SingletonLazy instance = null;  //volatile防止指令重排序private static Object locker = new Object();public static SingletonLazy getInstance() {synchronized (locker) {                 //使得if和创建实例变成一个整体的原子操作,防止多个线程同时操作时创建多个实例if (instance == null) {             //判断是否有实例创建了instance = new SingletonLazy();}}return instance;}private SingletonLazy() {}
}
public class Demo20 {   //懒汉模式public static void main(String[] args) {SingletonLazy t1 = SingletonLazy.getInstance();SingletonLazy t2 = SingletonLazy.getInstance();System.out.println(t1 == t2);}
}

OK~~~这下这两个问题也解决啦(●'◡'●)

这样看起来,线程好像是没啥安全问题了勒,那有没有啥可以优化的?

经典生活例子

为了使大家更好理解我们的优化策略,在po出代码之前,我们先搞个生活化例子理解一下~~

比如说学校里面的校花突然恢复单身了,那广大单身男青年们听到这个消息之后都很开心呐,不过大家素质都很高,于是在追求校花的时候排起了队,按序追求,你很幸运的抢到了第一个,你开始追求校花之后就相当于追求校花这个操作加锁了,别人就不能进行了,校花觉得你特别好,于是答应和你在一起啦~~[成功创建了实例],但是队伍里面的人此时还不知道,第二个人此时又去追求校花,追求校花这个操作就又加锁了,这个时候她就说我有男朋友啦,第二个人出去是不是就会告诉其它人,校花有男朋友了这件事情,那么实际上,他们就不会再进行追求校花这个操作了,即甚至连竞争锁这个操作都不会去做,因为这是浪费资源的

所以与例子同样的问题,我们这个代码此时每个线程都还会再去竞争一次锁,但如果实例已经存在了,就没有竞争锁的必要了,只有一开始的几个线程在实例被创建好之前就进入创建实例过程会竞争一下锁~~~

解决重复竞争锁问题
class SingletonLazy {private static volatile SingletonLazy instance = null;  //volatile防止指令重排序private static Object locker = new Object();public static SingletonLazy getInstance() {if (instance == null) {                     //后续线程可以防止重复加锁synchronized (locker) {                 //使得if和创建实例变成一个整体的原子操作,防止多个线程同时操作时创建多个实例if (instance == null) {             //判断是否有实例创建了instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}
public class Demo20 {   //懒汉模式public static void main(String[] args) {SingletonLazy t1 = SingletonLazy.getInstance();SingletonLazy t2 = SingletonLazy.getInstance();System.out.println(t1 == t2);}
}

标准懒汉模式实现代码

我们这里总结一下解决完所有问题之后的代码

class SingletonLazy {private static volatile SingletonLazy instance = null;  //volatile防止指令重排序private static Object locker = new Object();public static SingletonLazy getInstance() {if (instance == null) {                     //后续线程可以防止重复加锁synchronized (locker) {                 //使得if和创建实例变成一个整体的原子操作,防止多个线程同时操作时创建多个实例if (instance == null) {             //判断是否有实例创建了instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
}
public class Demo20 {   //懒汉模式public static void main(String[] args) {SingletonLazy t1 = SingletonLazy.getInstance();SingletonLazy t2 = SingletonLazy.getInstance();System.out.println(t1 == t2);}
}

❤❤感谢观看~~对你有帮助的话给博主点个大大的赞吧(●'◡'●)谢谢~~~❤❤

http://www.dtcms.com/a/332664.html

相关文章:

  • CANDB++中的CAN_DBC快速编辑方法,使用文本编辑器(如notepad++和VScode)
  • Redis 知识点与应用场景
  • 六十六、【Linux数据库】MySQL数据导入导出 、 管理表记录 、 匹配条件
  • 日本服务器哪些服务商是可以免费试用的?
  • 拒绝“效果图”返工:我用Substance 3D Stager构建产品可视化工作流
  • 计算机视觉(opencv)实战五——图像平滑处理(均值滤波、方框滤波、高斯滤波、中值滤波)附加:视频逐帧平滑处理
  • vue2生命周期详解
  • Claude Opus 4.1深度解析:抢先GPT5发布,AI编程之王主动出击?
  • 【线上问题】1分钟学会如何定位 Java 应用 CPU 飙升问题
  • Spring中存在两个相同的Bean是否会报错?
  • Amazon Bedrock如何轻松实现复杂的生成式AI模型?
  • 纯C++实现halcon的threshold
  • 【Java EE进阶 --- SpringBoot】初识Spring(创建SpringBoot项目)
  • zynq代办事项
  • Vue 侦听器(watch 与 watchEffect)全解析2
  • 【100页PPT】数字化转型集团信息化总体解决方案(附下载方式)
  • Swift 实战:用最长递增子序列算法解“俄罗斯套娃信封”问题(LeetCode 354)
  • 日本服务器租用选哪个机房国内访问比较快?
  • 【LINUX网络】HTTP协议基本结构、搭建自己的HTTP简单服务器
  • 企微用户部门同步HRS系统
  • 电脑上练打字用什么软件最好:10款打字软件评测
  • 滑窗|贪心
  • Sonatype Nexus Repository Manager docker版本安装
  • [优选算法专题二滑动窗口——无重复字符的最长子串]
  • Linux应用层开发--线程
  • react性能优化之useRef和useState
  • Nginx性能优化与安全配置:打造高性能Web服务器
  • Unity:PlayerPrefs笔记
  • 标准电子邮件地址格式(RFC 5322 里的 mailbox 语法)
  • ABAP : 内表/工作区转JSON