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

文章目录

  • 单例模式
    • 一、单例模式
      • 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的图

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/548163.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

艾体宝方案 | ITT-Profitap IOTA——铁路运输的远程网络捕获和故障排除方案

在移动互联时代,铁路运输的数字化转型已成不可逆转的趋势。然而,随之而来的是对网络连接质量和故障排查的更高要求。本文将探讨如何利用艾体宝Profitap IOTA技术,在火车上实现远程网络捕获和故障排查,助力铁路运输行业迈向智能化未…

✌粤嵌—2024/4/16—x的平方根

代码实现&#xff1a; int mySqrt(int x){if (x 0 || x 1) {return x;}long int i;for (i 1; i < x / 2; i) {if (i * i < x && (i 1) * (i 1) > x) {break;}}return i; }

关于小米消金-我以为的小米消金和实际的小米消金

我原以为的小米消金&#xff0c;是一个涵盖多种金融服务的平台&#xff0c;与小米品牌紧密相连&#xff0c;提供便捷的消费金融服务。然而&#xff0c;实际的小米消金&#xff0c;其业务范围、服务细节以及运营模式都与我之前的想象有所不同。它更注重用户体验&#xff0c;提供…

KVM虚拟机

文章目录 QEMU-KVM介绍虚拟网卡流程网卡透访流程 QEMU-KVM介绍 QEMU ● QEMU是一个主机上的VMM (Virtual machine monitor), 通过动态二进制模拟CPU&#xff0c;并提供一系列的硬件模型&#xff0c;使Guest OS能够与Host硬件交互。 ● QEMU的代码中有完整的虚拟机实现&#xf…

当路由模式为hash怎么对接京东物流回调链接

为什么我要写这篇文章呢&#xff1f;是因为最近在做这个需求&#xff0c;京东物流是需要自己手动点击授权后把返回的code拿去授权&#xff0c;刚开始以为一切都很顺利的&#xff0c;然而等我看到路由为hash时候&#xff0c;浏览器会忽略#后面的东西&#xff0c;所以是无法重新跳…

华三交换机知道ip怎么查找主机ip在接入交换机哪个端口下

环境&#xff1a; 华三S5120V3-52S-SI H3C Comware Software, Version 7.1.070, Release 6329 问题描述&#xff1a; 华三交换机知道ip怎么查找主机ip在接入交换机哪个端口下 已知主机ip192.168.1.111 解决方案&#xff1a; 在H3C&#xff08;新华三&#xff09;交换机上…

宝宝洗衣机买什么样的好?力荐四大表现突出婴儿洗衣机总汇

对于那些追求品质生活、分类洗涤的用户而言&#xff0c;专用的婴儿洗衣机可以满足我们对不同类型的宝宝衣物分开洗涤的需求&#xff0c;如果您家中有宝宝&#xff0c;或者您对个人卫生和健康有较高要求&#xff0c;这样的婴儿洗衣机可以提供高温洗涤程序&#xff0c;有效杀灭细…

单细胞核转录组——植物:叶叶我呀裂开啦~

单细胞转录组测序技术能够在单细胞分辨率下研究样本的转录组信息&#xff0c;可以完美解决细胞异质问题&#xff0c;能全面真实揭示细胞多样性和复杂性&#xff0c;能够更加深入地研究细胞类型、细胞功能、细胞亚群及其异质性、细胞谱系等&#xff0c;还常用于识别新的细胞类型…

【ARM 裸机】I.MX 启动方式之启动设备的选择

启动方式从两个方面来看&#xff0c;一是启动设备的选择&#xff0c;二是启动头文件的选择&#xff0c;本节是关于启动设备的选择&#xff1b; 1、硬件启动设备 回想一下上一节&#xff1a;【ARM 裸机】汇编 led 驱动之烧写 bin 文件&#xff0c;我们提到&#xff08;将 SD卡…

Springboot框架——3.整合SpringMVC

1.修改端口号&#xff1a; 在application.properties中添加如下配置即可&#xff1a; server.port8088 2.静态资源访问&#xff1a; 首先打开ResourceProperties这个类的源码&#xff1a; 将静态资源放到类中默认位置即可实现访问&#xff1a; http://localhost:8088/erth.jp…

bugku-web-xxx二手交易市场

这里直接来到一个交易平台 还是三板斧 源码查看 <script>$(#searchList li).click(function(){var type$(this).attr(aman-type);$(this).parent().find(li).attr(class,red);$(this).attr(class,red-on)search();});function search(){var url/index;var type$("#…

java通过maven导入本地jar包的三种方式

一、引入lib下加载&#xff08;加载过后打包&#xff0c;以后再次使用不用再次导入&#xff09; 首先创建一个用于创建jar包的项目&#xff0c;并测试能否成功运行 讲项目打包 在需要引入的项目中创建lib目录 并把刚才打包的jar复制进去 通过dependency引入jar包 groupId、art…

【安装部署】Apache SeaTunnel 和 Web快速安装详解

版本说明 由于作者目前接触当前最新版本为2.3.4 但是官方提供的web版本未1.0.0&#xff0c;不兼容2.3.4&#xff0c;因此这里仍然使用2.3.3版本。 可以自定义兼容处理&#xff0c;官方提供了文档&#xff1a;https://mp.weixin.qq.com/s/Al1VmBoOKu2P02sBOTB6DQ 因为大部分用…

详解数据中台,附案例

&#xff08;一&#xff09;什么是数据中台&#xff0c;有什么价值&#xff1f; 数据中台居于前台和后台之间&#xff0c;是企业级的数据共享、能力复用平台&#xff0c;是数字 化转型的基础和中枢系统。将企业全域海量、多源、异构的数据整合资产化&#xff0c;为业务前台提供…

使用TVM在树莓派部署yolov8模型(二)

这部分主要记录部署环境的搭建。 在电脑或服务器上编译&#xff0c;需要搭建完整的TVM工具链&#xff1b;树莓派上仅负责运行&#xff0c;因此只编译runtime即可。 先从简单的开始 一 在树莓派上编译runtime 我的树莓派是新装的系统&#xff0c;那就从头搭建环境吧。 我用的树…

发布!DolphinDB 白皮书正式上线官网!

对广大数据库用户而言&#xff0c;白皮书是极具参考价值的使用指南和学习手册。白皮书不但能深入剖析数据库的基础概念与架构&#xff0c;协助用户了解数据库的工作原理和应用技巧&#xff0c;更提供了丰富的实践案例&#xff0c;帮助用户从中汲取经验&#xff0c;避免在实际应…

在微信上怎么实现预约功能

在快节奏的现代生活中&#xff0c;时间成为了我们最宝贵的财富。无论是看病、理发、还是参加各类培训&#xff0c;等待往往成为了一个难以避免的“拦路虎”。为了解决这一痛点&#xff0c;微信预约功能应运而生&#xff0c;它不仅为我们的生活带来了极大的便利&#xff0c;更成…

文章解读与程序——资源回顾

综合能源是指利用多种能源形式&#xff0c;如化石能源、可再生能源和核能等&#xff0c;通过综合、灵活的方式满足能源需求。这种综合利用能源的方式旨在提高能源利用效率&#xff0c;减少能源消耗对环境的影响&#xff0c;并促进能源系统的可持续发展。 电力系统规划是指对电…

Android,AMS、WMS、PKMS添加动态控制debug开关功能

问题背景 在framework源码中有很多debug开关,通常我们想要看某个模块的日志,比如说广播,就需要去修改源码,把对应的debug值改为true,但是这种方法耗时耗力,比如说我想看sendBroadcast的流程,但是BroadcastQueue中有很多debug开关,如下: 这种就需要去修改对应的源码才…

PFA移液枪头为什么比普通塑料枪头贵这么多?

移液枪头是用于移液器的一次性适配枪头&#xff0c;可在各种研究应用中准确、精确、一致地转移少量液体。 PFA移液枪头&#xff0c;金属元素空白值低&#xff08;铅、铀含量小于0.01ppb)&#xff0c;化学性能好&#xff0c;可重复使用。 目前市场上的规格&#xff1a;0.01ml、…