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

单例模式

什么是设计模式呢?

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

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

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

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

 饿汉模式

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

实现方式:在该类中,使用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.mzph.cn/news/884115.shtml

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

相关文章

第十九章 Vue组件之data函数

目录 一、引言 二、示例代码 2.1. 工程结构图 2.2. main.js 2.3. App.vue 2.4. BaseCount.vue 三、运行效果 一、引言 在Vue CLI脚手架中一个组件的data选项必须是一个函数,以此保证每个组件实例,维护独立的一份数据对象。每次创建新的组件实…

mabtisx突然不起作用:mapper跳转不到xml

解决:进入官方下载:JetBrains Marketplace 选择和你idea对应的版本号 切内网下载

ComfyUI正式版来袭!一键安装无需手动部署!支持所有电脑系统

ComfyUI V1重磅发布:迈向AI图像处理新时代 近日,ComfyUI团队正式推出了备受期待的ComfyUI V1版本,为图像生成和处理的AI用户带来了全新的生态体验。这一版本不仅彻底解决了以往版本中繁琐的安装和兼容问题,还大幅提升了易用性&…

pycharm设定代码模板

1、在文件点击设置 ​​​​​​​ ​​​​​​​ 2、点击编辑器--实时模板--找到需要插入模板的位置如我要插入HTML的模板---选择--点击实时模板 3、如图: 4、添加模板内容,如果模板有变量可以在编辑变量处点击编辑 5、编辑变量 6、点…

蘑菇书(EasyRL)学习笔记(2)

1、序列决策 1.1、智能体和环境 如下图所示,序列决策过程是智能体与环境之间的交互,智能体通过动作影响环境,环境则返回观测和奖励。智能体的目标是从这些反馈中学习出能最大化长期奖励的策略,这一过程通过不断试错和调整实现强化…

SQL进阶技巧:如何利用三次指数平滑模型预测商品零售额?

目录 0 问题背景 1 数据准备 2 问题解决 2.1 模型构建 (1)符号规定 (2)基本假设 (3)模型的分析与建立 2.2 模型求解 3 小结 0 问题背景 1960年—1985年全国社会商品零售额如图1 所示 表1全国社…

Rsync远程同步详细介绍

一、rsync介绍 rsync是一款开源的、快速的、多功能的、可实现全量及增量的本地或远程数据同步备份的优秀工具。并且可以不进行改变原有数据的属性信息,实现数据的备份迁移特性。 rsync软件支持跨平台,适用于unix/ linux/windows等多种操作系统平台。rsyn…

无人机之自动控制原理篇

一、飞控系统 无人机飞控是指无人机的飞行控制系统,是无人机的大脑。飞控系统通过传感器、控制器和执行机构三部分实现对无人机的自动控制。 传感器:传感器负责收集无人机的姿态、速度、高度等信息。常见的传感器包括陀螺仪、加速度计、磁力计、气压计、…

得物App获评新奖项,正品保障夯实供应链创新水平

近日,得物App再度获评新奖项——“2024上海市供应链创新与应用优秀案例”。 本次奖项为上海市供应链领域最高奖项,旨在评选出在供应链创新成效上处于领先地位、拥有成功模式和经验的企业。今年以来,得物App已接连获得“上海市质量金奖”、“科…

STM32F103C8T6学习笔记3--按键控制LED灯

1、实验内容 S4、S5分别接PB12和PB13,实验要求,按下S4,D1亮,D2灭;按下S5,D2亮,D1灭。 由于按键学习的是GPIO口的输入功能,和输出功能的配置略有区别。本次通过按键触发相应功能没有…

10.27复习day —— 药销系统分页前(上)

登陆界面 1.auto 水平方向 —— 占据尽量多的空间 margin:auto —— 水平居中 2.块状:block main、div、p 特点: 独占一行 水平方向占满父元素可用空间(所以得设置宽度) 垂直方向占据空间由其内容大小决定 行内:inlin…

clickhouse运维篇(三):生产环境一键生成配置并快速部署ck集群

前提条件:先了解集群搭建流程是什么样,需要改哪些配置,有哪些环境,这个文章目的是简化部署。 clickhouse运维篇(一):docker-compose 快速部署clickhouse集群 clickhouse运维篇(二&am…

快速入门kotlin编程(精简但全面版)

注:本文章为个人学习记录,如有错误,欢迎留言指正。 目录 1. 变量 1.1 变量声明 1.2 数据类型 2. 函数 3. 判断语句 3.1 if 3.2 when语句 4. 循环语句 4.1 while 4.2 for-in 5. 类和对象 5.1 类的创建和对象的初始化 5.2 继承 5…

云原生Istio基础

一.Service Mesh 架构 Service Mesh(服务网格)是一种用于处理服务到服务通信的专用基础设施层。它的主要目的是将微服务之间复杂的通信和治理逻辑从微服务代码中分离出来,放到一个独立的层中进行管理。传统的微服务架构中&#x…

【网页布局技术】项目五 使用CSS设置导航栏

《CSSDIV网页样式与布局案例教程》 徐琴 目录 任务一 制作简单纵向导航栏支撑知识点1.合理利用display:block属性2.利用margin-bottom设置间隔效果3.利用border设置特殊边框 任务二 制作简单横向导航栏任务三 制作带图片效果的横向导航栏任务…

银河麒麟v10 xrdp安装

为了解决科技被卡脖子的问题,国家正在大力推进软硬件系统的信创替代,对于一些平时对Linux操作系统不太熟练的用户来讲提出了更高的挑战和要求。本文以银河麒麟v10 24.03为例带领大家配置kylin v10的远程桌面。 最近公司为了配置信创开发新购了几台银河麒…

什么是x86架构,什么是arm架构

什么是 x86 架构? x86 架构是一种经典的指令集架构(ISA),最早由英特尔在 1978 年推出,主要用于 PC、服务器等领域。 它是一种复杂指令集计算(CISC)架构,支持大量的复杂指令和操作&…

客户的奇葩要求—在CAN网络的基础上加入了CAN_FD的节点

1:客户的奇葩要求 最近的工作中,遇到了一个有点奇葩的事,客户需要开发一个系统,我们负责其中的一个ECU,这个系统采取的是经典11bit ID的CAN网络。 今天突然提了一个要求,说要在网络中,加入支持…

4G 模组的 FTP 应用:技术科普

众所周知FTP协议包括两个组成部分,其一为FTP服务器,其二为FTP客户端,今天我将为大家带来一场4G 模组的 FTP 应用技术科普: 以低功耗模组Air780E核心板为例。 1、FTP 概述 FTP(File Transfer Protocol,文件…

PAT甲级-1074 Reversing Linked List

题目 题目大意 给一个链表的头结点和总节点个数,以及k。每k个节点的链表都要翻转。 思路 链表可以用一个结构体数组来存储,先遍历一遍,过滤掉不在链表中的节点。然后将过滤好的节点放入res数组中,每k个元素用一次reverse()&…