[线程]单例模式 及 指令重排序

news2024/9/20 6:17:02

文章目录

  • 一. 单例模式
    • 饿汉模式
    • 懒汉模式
    • 单例模式中涉及到的线程安全问题
  • 二. 指令重排序引起线程安全问题

一. 单例模式

单例模式, 是一种经典的设计模式

设计模式:
类似于棋谱, 把编程中各种经典的问题场景给你盘一盘, 并给出一下解决方案
遇到这种场景, 代码就这样写, 绝对不会很差

单例模式, 就是指单个实例, 整个进程中的某个类, 有且只有一个对象(不能new很多对象)
例如: 某些业务角度, 有些对象就应该是单例的
比如, 你写的服务器, 要从硬盘上加载100G的数据到内存中, 肯定要写一个类, 封装上述加载操作, 并且写一些获取 / 处理数据的业务逻辑, 这样的类, 就应该是单例的, 因为一个实例, 就要管理100G的内存数据, 搞多个实例, 就是N * 100G的内存数据, 机器肯定吃不消, 也没必要搞这么多重复的

保证类只有一个实例, 需要让编译器来帮我们做一个强制的检查, 通过一些编码上的技巧, 使编译器可以发现咱们代码中是否有多个实例, 并且在尝试创建多个实例的时候, 直接编译出错

单例模式, 有很多种不同的写法, 这里我们只介绍两种: 饿汉模式, 懒汉模式
用Singleton类表示单例模式, instance作为实例

饿汉模式

第一步:
在这里插入图片描述
static修饰的, 其实是"类属性", 就是在"类对象"上的, 每个类的类对象在JVM中只有一个, 那么里面的静态成员就只有一份了
static成员初始化的时机是在类加载的时候, 此处可以简单理解为, JVM一启动, 就立即加载
此时, instance就是一个实例(引用), 该实例只有一份(static修饰)
第二步:
在这里插入图片描述
后续, 我们想要拿到这个对象, 直接调用getInstance, 而不是new
第三步:
在这里插入图片描述
将构造方法设成private, ,这样就防止后续又new了这个类的实例
类之外的代码, 尝试new的时候, 势必要调用构造方法, 但是由于构造方法是私有的, 无法调用, 就会编译出错!!

懒汉模式

懒汉模式, 不是在程序启动的时候创建实例, 而是砸以第一次使用的时候才去创建(如果不使用了, 就会把创建实例的代价节省下来)
在这里插入图片描述
这样用一个条件判断, 就保证了在第一次调用的时候才创建对象, 如果没调用, 就不创建了

如果代码中存在多个单例类, 使用饿汉模式, 就会导致这些实例都是在程序启动的时候扎堆创建的, 可能把程序启动的时间拖慢
如果是懒汉模式, 啥时候首次调用, 啥时候创建, 调用时机是分散的, 就不太会出现卡顿问题

单例模式中涉及到的线程安全问题

结合线程不安全的原因:1. 线程抢占式执行 2. 多个变量同时修改同一个变量 3. 修改操作不是原子的
饿汉模式:
在这里插入图片描述
创建实例的时机是在java进程启动时, 比main调用的时机还早, 那么后续代码里创建线程, 一定比实例创建还要迟, 后续线程调用getInstance的时候, 意味着上述实例早就有了, 而getInstance只干一件事, 就是读取上述静态变量的值, 此时多个线程读取同一变量, 是线程安全

懒汉模式:
在这里插入图片描述
上述代码, 在cpu上的执行顺序, 可能如下:
在这里插入图片描述
此时t1已经new了之后, t2会继续在new一次

此时, instance是static修饰的, 只有一个引用
执行t1时, instance指向t1new的对象, 执行t2后, 不会创建其他引用, 还是instance指向t2new的对象, 会覆盖掉t1对象
此时t1对象没有引用指向了, 所以就会被GC回收
但是虽然还是一个对象, 但是毕竟是创建过, 时间开销还是客观存在的

此时, 我们可以通过加锁来解决这个问题
把if和new打包成一个原子操作:
在这里插入图片描述
此时, t2只能等t1创建完对象之后, 在判断条件, 此时条件不成立, 直接返回t1创建的对象了

但是, 加锁是个非常低效的操作, 我们只有在第一次调用getInstance方法时, 才需要加锁, 后续再调用, 只是读操作, 并不存在线程安全问题, 不必加锁了

所以我们对代码进行改进:
在这里插入图片描述
加上判断条件, 如果此时是第一次调用getInstance, 那么就需要加锁

外层条件是判断是否需要加锁
里层条件是判断是否需要new对象
碰巧两次条件相同, 但是缺一不可!!

但是, 上述代码还可能会涉及到内存可见性问题
比如, t1线程修改了instance引用, t2有可能读不到, 但是这种概率比较小
若依我们可以给对象加上volatile

另一方面, volatile不仅能够解决内存可见性问题, 还可以解决指令重排序的问题

二. 指令重排序引起线程安全问题

指令重排序, 也是编译器的一种优化策略
编译器有很多种优化策略, 把读内存优化到读寄存器, 指令重排序, 循环展开, 条件分支预测…

我们写的代码, 最终会编译成一系列的二进制指令, 正常来说, CPU应该是按照顺序, 一条一条的执行的
但是编译器比较智能, 会根据实际情况, 生成二进制指令的执行顺序, 和你最初写的代码的顺序可能会存在差异, 但是前提是逻辑是等价的, 这么优化的目的就是为了提高效率

在单线程下, 编译器进行指令重排序的操作, 一般都是没问题的, 编译器能准确的识别出, 哪些操作可以重排序, 而不会影响到逻辑
但是, 多线程下, 判定就可能不准确了, 就可能会出现重排序后, 逻辑发生了改变, 从而引起bug
在这里插入图片描述
这行代码, 其实还可以简单分成三个步骤(如果从指令的维度来看, 这里其实有几十条指令, 不止):
1.申请内存空间
2.调用构造方法(对内存空间进行初始化)
3.把此时内存空间的地址, 赋值给instance引用

在指令重排序的优化策略下, 上述执行的顺序不一定是123, 可能是132
在这里插入图片描述
上述执行顺序, 就出现了bug

要想解决上述问题, 就需要引入volatile
volatile不仅仅能够解决内存可见性问题, 也能针对对象读写操作的指令重排序问题(在很多地方都会发生重排序, volatile特指的是, 针对某个对象的读写操作过程中, 不会发生重排序)

其实, 指令重排序问题很能验证, 本身就是一个 小概率事件, 即使不加volatile, 运行很多次, 也是正确的, 但是指不定啥时候会出现问题, 我们能做的就是把volatile该加的加上即可

面试中让你现场写一个单例模式代码
正确的写法:
先写最初的版本 -> 加上锁 -> 加上双重if -> 加上volatile

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

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Linux和Unix的区别及为什么鸿蒙系统不用Unix的原因

目录 Linux是什么? Unix是什么? 他们的区别: 鸿蒙系统介绍及鸿蒙系统不用Unix的原因 Linux是什么? Linux的历史可以追溯到1991年,由芬兰的计算机科学家林纳斯托瓦兹(Linus Torvalds)为了学习操作系统的工作原理而…

【 OpenHarmony 系统应用源码魔改 】-- Launcher 之「桌面布局定制」

前言 阅读本篇文章之前,有几个需要说明一下: 调试设备:平板,如果你是开发者手机,一样可以加 Log 调试,源码仍然是手机和平板一起分析;文章中的 Log 信息所显示的数值可能跟你的设备不一样&…

STM32GPIO操作底层解析

我们使用HAL 和 标准库时,常常忽略他两的底层,只知道怎么用不知其原理,其实是大忌,因为底层丢了代码的灵魂就丢了,对以后的Linux开发不利 常用的指令函数: void GPIO_WriteBit(GPIO_TypeDef* GPIOx, uint…

本地编写Markdown格式文件,浏览器查看

编写准备 下载VsCode并安装,打开后在内部安装Markdown All in One、Markdown Preview Enhanced、Paste Image三个插件。新建一个文件夹用以后期保存你的笔记等文件在左侧新建文件,.md结尾,即完成创建右侧可实时的查看你的编写结果&#xff0…

SpringBoot3集成Spring Authorization Server实现SSO单点登录

1. 概述 在之前的文章中介绍过SpringBoot集成OAuth2老版本的方案SpringCloud搭建微服务之OAuth2实现SSO单点登录,随着Spring Authorization Server框架的成熟和SpringBoot版本的更新,新项目必然会采用新的技术和框架,本文将使用最新的Spring…

推理引擎测试-算力共享:test_inference_engine

目录 算力共享:test_inference_engine 关键点解释 实际应用和注意事项 算力共享:test_inference_engine 这段代码设计用于测试一个名为 InferenceEngine 的推理引擎,特别是测试其在处理不同分片(Shards)时的连续性和一致性。在机器学习和深度学习模型中,尤其是当模型非…

jenkins+python+appium 本地(简洁版)

jenkinspythonappium 本地(简洁版) 等服务器到了配到服务器上去,先在自己电脑上试一下。踩了n个坑,网上找的资料太深奥TAT 直接上操作 先把jenkins安装好(肯定安装啦)我的版本是2.462 新建任务 输入名称 选…

市占率最高的显示器件,TFT_LCD的驱动系统设计--Part 1

目录 一、简介 二、TFT-LCD驱动系统概述 (一)系统概述 (二)设计要点 二、扫描驱动电路设计 (一)概述 扫描驱动电路的功能 扫描驱动电路的组成部分 设计挑战 驱动模式 (二&#xff09…

互联网全景消息(1)之RabbitMq基础入门

一、消息中间件 1.1消息队列回顾 消息队列中间件是分布式系统中重要的组件,主要解决应用解耦,异步消息,流量削锋等问题,实 现高性能,高可用,可伸缩和最终一致性架构。目前使用较多的消息队列有ActiveMQ &a…

js插件-模糊搜索、自动补全下拉框

问题&#xff1a;一个老系统&#xff0c;让把所有jsp页面动态生成的<select>下拉选&#xff0c;选项过多的下拉选全部改为支持模糊搜索的下拉选的功能。系统框架只有 jq 和layui&#xff08;仅用于列表和弹窗&#xff09;&#xff0c; JQurey 首先想到的就是jQuery UI …

构建以数据为核心智慧型工业园区新架构方案

1. 项目背景与目标 智慧型工业园区新架构的构建旨在通过数据驱动实现节能、绿色、高效和安全的目标&#xff0c;以应对当前工业园区在基础数据收集、系统管理和操作复杂性方面的挑战。 2. 现状分析 当前工业园区的发展面临数据收集难题、系统分散、操作复杂以及孤岛效应&…

前端面试——八股文

一、Vue2篇 1. 关于生命周期 1.1 生命周期有哪些&#xff1f;发送请求在created还是mounted&#xff1f; 请求接口测试&#xff1a;https://fcm.52kfw.cn/index.php?_mall_id1&rapi/default/districtVue2.x系统自带有8个 beforeCreate created beforeMount mounted be…

电子签合同区块链存证合约小程序开源版开发

电子签合同区块链存证合约小程序开源版开发 电子合同底层对接的腾讯电子签接口&#xff0c;支持自定义模版发起合同和文件发起合同&#xff0c;支持骑缝章&#xff0c;多方签署&#xff0c;腾讯至信链提供区块链存证&#xff0c;安全高效签署合同文书。 特色功能 自定义合同模…

(计算机论文)基于SpringBoot和Vue的台球赛事服务网站的设计与实现

毕业设计&#xff08;论文&#xff09; 博主可接毕设论文&#xff01;&#xff01;&#xff01; 基于SpringBoot和Vue的台球赛事服务网站的设计与实现 摘 要 在快速发展的信息时代&#xff0c;体育竞赛作为群众文化娱乐的一部分&#xff0c;已日益受到广泛关注。台球&#xff…

Windows—线程基本知识和线程同步

线程 线程的组成 线程的内核对象&#xff0c;操作系统用它来对线程实施管理。内核对象也是系统用来存放线程统计信息的地方。线程堆栈&#xff0c;它用于维护线程在执行代码时需要的所有函数参数和局部变量 线程的进入点 每个线程必须拥有一个进入点函数&#xff0c;线程从…

备战2024年全国大学生数学建模竞赛:多波束测线问题的解题与优化

目录 一、引言 二、问题分析 三、解题思路与模型建立 问题1&#xff1a;覆盖宽度及重叠率计算 问题2&#xff1a;不同测线方向的覆盖宽度 问题3&#xff1a;最短测线的设计 问题4&#xff1a;基于单波束数据的测线设计 四、知识点解析 五、结果讨论与总结 六、模型的评…

X86架构(六)——硬盘访问与控制

在前面几节中&#xff0c;我们总是通过ROM-BIOS从硬盘的主引导扇区读取一段程序并加载到内存运行&#xff0c;但是处理器是如何访问硬盘呢&#xff1f;这是一个值得我们思考的问题 OK&#xff0c;我们先看一张图 所有这些和计算机主机连接的设备&#xff0c;叫做外围设备,也叫…

240831-Qwen2-VL-7B/2B部署测试

A. 运行效果 B. 配置部署 如果可以执行下面就执行下面&#xff1a; pip install githttps://github.com/huggingface/transformers accelerate否则分开执行 git clone https://github.com/huggingface/transformers cd transformers pip install . accelerate随后&#xff0…

8.27FLEX,BISON

RC ParseStage::handle_request(SQLStageEvent *sql_event) 这个意思是返回类型是RC&#xff0c;然后用到的函数来自 ParseStage&#xff0c;&#xff1a;&#xff1a;就是用来标识作用域的&#xff0c;函数名是handle_request&#xff0c;是ParseStage里的函数 FLEX BISON

vue.js项目实战案例详细源码讲解

​ 大家好&#xff0c;我是程序员小羊&#xff01; 前言&#xff1a; 为帮助大家更好地掌握Vue.js项目的开发流程&#xff0c;我将为你讲解一个完整的Vue.js实战案例&#xff0c;并提供详细的源码解析。这个案例将涵盖从项目创建到实现各种功能模块的全过程&#xff0c;适合用于…