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

多线程案例---单例模式

单例模式

什么是设计模式呢?

设计模式就好比棋手中的棋谱。在日常开发中,会会遇到很多常见的“问题场景”,针对这些问题场景,大佬们就设计了一些固定套路,按照这些固定套路来实现代码或应对这些问题场景,也不会吃亏。这些固定的套路就是设计模式

单例模式就是设计模式中的一个非常经典的一个设计模式,也是校招中最容易考到的设计模式。

单例模式可以保证某个类在程序中只有唯一 一个实例化对象,不会存在多个实例化对象。

单例模式的实现方式有很多种,最常见的有“饿汉模式”“懒汉模式”这两种。

 饿汉模式

饿汉模式就是讲究一个迫切,在饿汉模式中,要求在加载类的同时,创建实例

实现方式:在该类中,使用static修饰类成员对象,并将该类的构造方法用private修饰,使其私有化,并提供一个方法来获得对象。

实现代码:

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

懒汉模式

 懒就是尽量晚的创建实例,甚至不创建实例,也就是延迟创建实例,这样就方便我们根据实际需求来创建合适的实例

实现方式:在该类中,先让static修饰的类成员置为null,然后通过方法来创建类的实例和获取实例。

代码实现

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

线程安全分析

 在多线程的情况下,饿汉模式和懒汉模式是否存在线程安全问题?

判断饿汉模式和懒汉模式是否存在线程安全问题,主要是分析这两种模式的getInstance()方法是否存在线程安全问题。

饿汉模式

由于懒汉模式中的getInstance()方法中只有一个return语句,这是一个读操作,所以不会涉及线程安全问题。

懒汉模式

由于懒汉模式中的getInstance()方法中涉及到修改操作,在多线程程序中,有可能产生线程安全问题。

1.原子性分析

如下图

 

 

如上图,在多线程情况下就会出现上图这种情况, 这样就会导致第一次new的对象被第二次new的对象给覆盖掉了,第一个线程new出来的对象就会被GC释放掉了。

这里假设我们我们new的过程中要将一个很大内存的数据加载到内存中,本来加载一份这样的数据就要花费很多时间,但由于上述问题的存在,可能就要加载两份的数据,结果第一份数据还被释放掉了,这样反而降低了程序的运行效率

这里产生线程安全的原因是条件判断和修改操作不是原子的,这时,我们就可以通过加锁,将判断操作和修改操作打包成原子的。

如下图

引入加锁后,后执行的线程执行到加锁的位置就会阻塞,等到前一个线程执行完毕释放锁时,此时,instance就不为null了,所以第二个线程就不会执行new操作了,这样就避免出现加载两份new出相同对象的情况了,提高了程序的效率。 

2.锁效率分析

 当我们加锁之后,就会引入一个新的问题。

当我们的instance已经实例化好之后,多个线程在继序执行代码的时候,为了判断instance是否已经实例化,就会多次的加锁去执行所里面的判断操作,多个线程持续的加锁和解锁就会出现阻塞,一旦阻塞,对于计算机来说,阻塞的时间间隔就是沧海桑田,这样影响程序的效率。

解决方法:

我们可以按需加锁,真正涉及到线程安全问题的时候我们在加锁,不涉及到线程安全问题的时候,我们就不加锁。

以上面的的的代码为例

我们真正涉及到线程安全问题的时候是第一次实例化instance的时候,当我们第一次实例化成功后,后面执行的线程就不必再去加锁,进去所里面执行实例化的操作了,也就是说,第一次instance成功实例化之后,后面线程涉及的操作就不涉及线程安全问题了,所以我们就可以让后面执行的线程跳过加锁的操作

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

注意:

这里出现了两次if(instance==null)

syncronized里面的if(instance==null)是为了判断是否需要实例化对象,最外面的if(instance==null)是为了在多线程程序中,instance已经实例化好的情况下,其他线程继续执行该代码的时候,不需要再继续实例化,让其跳过加锁和解锁的操作,直接执行return语句,使程序不会阻塞,提高程序的运行效率。

3.内存可见性分析

上面代码在多线程情况下会不会出现内存可见性的问题呢?

如下图

由于编译器优化是一个非常复杂的过程,我们无法确定是否出现内存可见性问题,但是为了杜绝内存可见性的问题,我们还是要用volatile关键字来修饰类成员 。

 

4.指令重排序分析 

这里更关键的问题,是指令重排序问题。

指令重排序也是编译器优化的一种体现,编译器优化能保证在代码逻辑不变的情况下,会改变代码指令执行的先后顺序来提高代码的运行效率。

如上面代码中的instance=new SingletonLazy();这个语句就有可能触发指令重排序的问题。

这条语句执行的指令主要有3条

1. 申请内存空间

2. 在空间中构造化对象,也就是初始化对象

3.将内存空间的“首地址”赋值给引用变量

正常的执行顺序是1->2->3,但是由于指令重排序的问题会出现1->3->2这样的执行顺序,也就是instance不为null了,但是还没初始化里面的内容,此时就会导致其他线程拿着一个未初始化的对象进行其他操作,这样就会导致线程安全问题。 

针对指令重排序的问题,我们也是通过volatile来修饰类成员来解决。

volatile关键字的作用

1.确保程序成内存中读取数据,避免内存可见性问题

2.确保程序对变量的读取和修改不会出现指令重排序的问题。 

懒汉模式的完整版代码

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

http://www.mrgr.cn/news/62634.html

相关文章:

  • 【Mac】Homebrew
  • 【Python各个击破】matplotlib
  • 构建您自己的 RAG 应用程序:使用 Ollama、Python 和 ChromaDB 在本地设置 LLM 的分步指南
  • 代码编辑器 | Visual Studio Code v1.95.0
  • node学习记录-os
  • Java MySQL-JDBC编程
  • 2024年NSSCTF秋季招新赛-WEB
  • Convolution 卷积
  • 前端笔面试查漏补缺
  • 鸿蒙系统:核心特性、发展历程与面临的机遇与挑战
  • JSP水果商城管理系统WEB项目
  • Vue中path和component属性
  • 宠物空气净化器是否有用?五大高性价比宠物空气净化器种草推荐
  • 前端如何安全存储密钥,防止信息泄露
  • 高级SQL技巧:优化查询与提升性能(附11个示例代码)
  • #HarmonyOS:名词
  • Leetcode 198. 打家劫舍 动态规划
  • 拆分PPOCRLabel标注的数据集并生成识别数据集
  • 动态规划-回文串问题——647.回文子串
  • Python使用 try-except 捕获与处理异常
  • 从安装到实战:Spring Boot与RabbitMQ的终极整合指南
  • Go 语言解析 yaml 文件的方法
  • ES聚合(仅供自己参考)
  • 【安全性分析】BAN逻辑 (BAN Logic)之详细介绍
  • 天润融通邀您参加AI破局·聚力增长行业论坛
  • 去人声留伴奏免费软件,这四款软件可别错过