【多线程】单例模式 | 饿汉模式 | 懒汉模式 | 指令重排序问题

2024-04-15 21:04

本文主要是介绍【多线程】单例模式 | 饿汉模式 | 懒汉模式 | 指令重排序问题,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 单例模式
    • 一、单例模式
      • 1.饿汉模式
      • 2.懒汉模式(单线程)
      • 3.懒汉模式(多线程)
        • 改进
      • 4.指令重排序
          • 1.概念
          • 2.question:
          • 3.解决方法
          • 4总结:

单例模式


一、单例模式

单例,就是单个实例

在有些场景中,希望有的类只能有一个对象,通过代码的语法规范,达到编译器强制检查的效果

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

比如很多用来管理数据的对象,就是单个实例的。

1.饿汉模式

//希望有唯一实例
class Singleton {//单例模式private static Singleton instance = new Singleton();//因为是static成员,在Singleton这个类被加载的时候,这里就会创建实例public static Singleton getInstance(){//通过getInstance方法来获取到这个实例return instance;}private Singleton(){}//将构造方法设置为私有的}public static void main(String[] args) {Singleton instance1 = Singleton.getInstance();//通过类名调用成员方法,获取到唯一实例Singleton instance2 = Singleton.getInstance();System.out.println(instance1 == instance2);//true}
  • 将构造方法设置为私有的,避免再次生成实例。只能通过getInstance()方法来获取类变量创建好的唯一实例
  • 唯一实例是在类加载的时候创建的(创建时间早)->饿汉模式(比较急)
  • 在饿汉模式中,如果在多线程中,多个线程同时读取同一个变量,是线程安全的。只读不修改。

2.懒汉模式(单线程)

比较从容,在第一次使用的时候,再去创建实例

class SingletonLazy{private static SingletonLazy instance = null;//先设置为空public static SingletonLazy getInstance(){if (instance == null){instance = new SingletonLazy();}return instance;}private SingletonLazy(){}}
  • 在首次调用getInstance方法的时候,才会真正创建唯一实例

    不调用就不会创建。

  • ”懒“意味着高效,省略不必要的操作和开销,只在需要的时候才开始进行。

在这里插入图片描述

  • 违背了单例的要求。
  • 懒汉模式在多线程环境下,涉及到了同一个变量的读取和修改,就存在线程安全问题。

3.懒汉模式(多线程)

解决方法:对if的判断操作和创建实例操作进行加锁,使两个操作始终执行在一起。

class SingletonLazy{private static SingletonLazy instance = null;//先设置为空public static  SingletonLazy getInstance(){synchronized(SingletonLazy.class){if (instance == null){instance = new SingletonLazy();}}return instance;}private SingletonLazy(){}
}
  • 虽然进行了加锁,但是每次再调用getInstance()方法的时候,都会进行加锁操作。
  • 而懒汉模式的线程安全问题,体现在第一次实例法创建。后续创建好实例后的所有调用操作,都是读操作,没有必要进行加锁。
  • 加锁是一个开销很大的操作,加锁就可能涉及到锁竞争,一冲突就会引发堵塞等待,涉及线程的调度。
改进

在加锁操作前,再进行一次判断

如果实例未创建,此时存在线程安全问题,需要加锁。如果对象已经创建,此时线程就是安全的,不需要加锁

    private static SingletonLazy instance = null;//先设置为空public static SingletonLazy getInstance() {if (instance == null) {//第一个if判断的是是否要加锁synchronized (SingletonLazy.class) {if (instance == null) {//第二个if判断的是,是否要new对象instance = new SingletonLazy();}}}return instance;}

首次拿到锁的那个进程,一定创建了这个唯一对象。等后续的进程拿到锁后,再次进行判断,就不会创建对象了。

指令重排序可能会对上述代码会产生影响。

4.指令重排序

1.概念

同样也是因为编译器的优化:

​ 编译器为了执行效率,在保证逻辑不变的前提下,可能会调整原来代码的执行顺序,从而提高效率。

在单线程下安全,多线程下可能会存在误判。

在多线程下,创建实例的操作时,new操作可能会引发指令重排序问题

new操作分为三步:第一步一定是先执行的,可能是1 、2、3 或者1、3、2的顺序;来执行。

1.申请内存空间

2.在内存空间上构造对象(执行构造方法)

3.把内存的地址,赋值给instance引用

就类似于:买房 装修 交钥匙 / 买房、交钥匙、装修 最终效果是一样的。

​ 假设线程1按照1、3、2的顺序来执行。当1和3执行完后,3直接进行赋值操作。此时,instance就不在是null了,而是指向的是一个还没有进行初始化的非法对象。此时1、3执行完,还没开始执行操作2,线程2就开始执行了 。线程2先对instance进行判断。此时intance==null 不成立,线程2直接返回instance。但是instance只是一个还没有进行初始化的非法对象。线程2获取的对象是不完全的。会产生严重的问题。

  • 也就是说,由于操作指令的执行顺序被优化了,导致实例创建的不完全就被调用了。表面提前符合了判断标准,但是内部还没有进行完全。
  • 就类似于:业主买的是精装房,直接交了钥匙,想要拎包入住时,发现没有进行装修还是毛坯房。实际上精装房是包含装修的。那么就是开发商的执行顺序出现了问题,没安排装修就交了钥匙~
2.question:

提问:既然线程1执行到new的过程中,就已经先加锁了。线程2还能new的1、3操作刚完时,就插进来执行吗?

​ 因为线程2的第一个if没有涉及到加锁操作,是完全可以执行的。锁的阻塞等待,一定是两个线程都加锁的时候才会触发。线程1拿到锁时,修改了instance的引用。此时线程2并没有参与锁的竞争,只是进行了第一个if的判断,非空情况下也不会进入if内部进行加锁操作,而是直接进行了返回。因此没有涉及任何阻塞等待。

3.解决方法

采用volatile,用volatile来修饰instence,保证instence在修改的过程中,就不会进行指令重排序的现象了。

class SingletonLazy {private static volatile SingletonLazy instance = null;//先设置为空public static SingletonLazy getInstance() {if (instance == null) {//第一个if判断的是是否要加锁synchronized (SingletonLazy.class) {if (instance == null) {//第二个if判断的是,是否要new对象instance = new SingletonLazy();}}}return instance;}private SingletonLazy() {}
4总结:

单例模式三步走:

1.懒汉模式下的双重if嵌套

2.用Synchronized对第二个if和后续是实例化操作进行加锁

3.用volatile修饰实例,禁止指令重排序。

面试遇到的话,人生如戏全靠演技,要适当藏拙,一步一步优化。从单线程的懒汉模式,到加锁,再到指令重排序

点击移步博客主页,欢迎光临~

偷cyk的图

这篇关于【多线程】单例模式 | 饿汉模式 | 懒汉模式 | 指令重排序问题的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/906914

相关文章

Java设计模式---迭代器模式(Iterator)解读

《Java设计模式---迭代器模式(Iterator)解读》:本文主要介绍Java设计模式---迭代器模式(Iterator),具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,... 目录1、迭代器(Iterator)1.1、结构1.2、常用方法1.3、本质1、解耦集合与遍历逻辑2、统一

Java 线程安全与 volatile与单例模式问题及解决方案

《Java线程安全与volatile与单例模式问题及解决方案》文章主要讲解线程安全问题的五个成因(调度随机、变量修改、非原子操作、内存可见性、指令重排序)及解决方案,强调使用volatile关键字... 目录什么是线程安全线程安全问题的产生与解决方案线程的调度是随机的多个线程对同一个变量进行修改线程的修改操

使用Python实现可恢复式多线程下载器

《使用Python实现可恢复式多线程下载器》在数字时代,大文件下载已成为日常操作,本文将手把手教你用Python打造专业级下载器,实现断点续传,多线程加速,速度限制等功能,感兴趣的小伙伴可以了解下... 目录一、智能续传:从崩溃边缘抢救进度二、多线程加速:榨干网络带宽三、速度控制:做网络的好邻居四、终端交互

Redis出现中文乱码的问题及解决

《Redis出现中文乱码的问题及解决》:本文主要介绍Redis出现中文乱码的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录1. 问题的产生2China编程. 问题的解决redihttp://www.chinasem.cns数据进制问题的解决中文乱码问题解决总结

全面解析MySQL索引长度限制问题与解决方案

《全面解析MySQL索引长度限制问题与解决方案》MySQL对索引长度设限是为了保持高效的数据检索性能,这个限制不是MySQL的缺陷,而是数据库设计中的权衡结果,下面我们就来看看如何解决这一问题吧... 目录引言:为什么会有索引键长度问题?一、问题根源深度解析mysql索引长度限制原理实际场景示例二、五大解决

Springboot如何正确使用AOP问题

《Springboot如何正确使用AOP问题》:本文主要介绍Springboot如何正确使用AOP问题,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录​一、AOP概念二、切点表达式​execution表达式案例三、AOP通知四、springboot中使用AOP导出

Python中Tensorflow无法调用GPU问题的解决方法

《Python中Tensorflow无法调用GPU问题的解决方法》文章详解如何解决TensorFlow在Windows无法识别GPU的问题,需降级至2.10版本,安装匹配CUDA11.2和cuDNN... 当用以下代码查看GPU数量时,gpuspython返回的是一个空列表,说明tensorflow没有找到

解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题

《解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘问题》:本文主要介绍解决未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4... 目录未解析的依赖项:‘net.sf.json-lib:json-lib:jar:2.4‘打开pom.XM

IDEA Maven提示:未解析的依赖项的问题及解决

《IDEAMaven提示:未解析的依赖项的问题及解决》:本文主要介绍IDEAMaven提示:未解析的依赖项的问题及解决,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录IDEA Maven提示:未解析的依编程赖项例如总结IDEA Maven提示:未解析的依赖项例如

一文详解Java Stream的sorted自定义排序

《一文详解JavaStream的sorted自定义排序》Javastream中的sorted方法是用于对流中的元素进行排序的方法,它可以接受一个comparator参数,用于指定排序规则,sorte... 目录一、sorted 操作的基础原理二、自定义排序的实现方式1. Comparator 接口的 Lam