线程安全之单例模式
文章目录
- 前言
- 一.什么是单例模式
- 二.在java中的单例模式
- 2.1 饿汉式的介绍
- 2.2 懒汉式的介绍
- 三 懒汉式的单例模式,线程不安全的解决方式
- 3.1 造成线程不安全的原因
- 3.2 解决方案
- 3.3 总结
前言
这篇文章,我们会介绍一下单例模式,但这里的单例模式,不是我们所说的设计模式,当然听到设计模式,大家一定都说,我当然知道设计模式了,有23种呢?一下子一顿输出,当然我这里说的单例模式还是跟设计模式有一些区别的,当然我不做概述,因为我也没咋个去了解过设计模式,我把大家拉回来,什么是多线程对的单例模式呢?看完我以下的解释相信你会明白的.
一.什么是单例模式
多线程环境下的单例模式需要保证只有一个实例对象被创建,并且可以在多线程环境下安全地访问该实例。
概念性的东西就是这样,把这段话,反复读,相信你也读不出来什么结果.简单来说的话,就是我现在只能娶一个老婆,不能娶多个老婆一样的意思,如果我娶多个老婆的话,在现在看来就是犯法的,当然了,小伙伴们,可不敢这样想,我想要后宫佳丽3000,这种想想就行了,哈哈
再来说说,在java中的单例模式的情况,Java 中的单例模式,借助java语法,保证某个类,只能够创建出一个实例,而不能new 多次.下面我们继续来看看在java中如何实现单例模式.
二.在java中的单例模式
怎么说呢?在Java中实现单例模式主要有以下几种:
- 饿汉式
- 懒汉式
- 双重检查锁
- 静态内部类
- 枚举
当然,我们或许没列举完,如果有其他的,也请各位小友,补充以下,当然我也不会全部介绍完,只会详细的介绍饿汉式和懒汉式.
2.1 饿汉式的介绍
饿汉式,大家通常想到饿这个词的,通常就会跟狼吞虎咽这个词语联系到一起,当然,我举个简单的例子,就拿取快递来说吧,你买了8件快递,今天到了2件,你马上就会去取快递.这就是饿汉式,遇事就马上下决定,真男人,绝不退缩,突出一个猛.
了解完什么是饿汉式,我们来看一看,饿汉式的代码实现.
//把这个类设置成单例的
public class Singleton {private static Singleton instant = new Singleton();//获取实例的方法public static Singleton getInstance() {return instant;}//禁止外部new实例private Singleton() {};
}
class ThreadDemo16{public static void main(String[] args) {//此时的s1// 此时 s1 和 s2 是同一个对象!!Singleton s1 = Singleton.getInstance();Singleton s2 = Singleton.getInstance();System.out.println(s1 ==s2);}}
简单来说,我们是怎么去实现这个饿汉式的单例模式的呢,主要进行了俩点,你注意看
我们这一句:
private static Singleton instant = new Singleton();是类内部把实例创建好.
另外一个步骤就是,我们在创建对象,进行实例化的时候,我们吧构造方法私有化.
private Singleton() {}
这俩个步骤,我们自然就实现了单例模式,保证只有一个实例对象被创建.
当然我们既然谈到线程,我们自然就要讨论一个问题,你觉的.我们饿汉式的方式是线程安全的吗?如果你看过,我写的那个线程安全的类别时候,我们就知道,是否会发生线程安全的问题了.
总的来说,这上面几种情况都是没有出现的,因此我们初步判断饿汉式是线程安全的,但是也不完全是,有些资料说会在类加载的时候就创建实例,如果该实例很大或者初始化过程很耗时,会造成资源浪费。这点值不值得考究,我就不知道了,但从表面上来说,我们并没有从饿汉式中看出什么端倪.
2.2 懒汉式的介绍
懒汉式,我们自然就注重一个懒字,那我们为啥要叫懒汉式呢?其实吧,我再拿快递的例子去解释一下,假设你买了八个东西,但突然有一个快递到了,但是你不会立刻去取,你要等到它们都到了,一起去取,这就是懒汉式,理解了这个概念之后,我们就看一下java代码的概念.
import com.sun.org.apache.regexp.internal.RE;class SingleLazy{private static SingleLazy instance=null;private SingleLazy(){}public static SingleLazy getInstance(){if (instance == null){instance=new SingleLazy();}return instance;}
}
public class ThreadDemo18 {public static void main(String[] args) {SingleLazy s1 = SingleLazy.getInstance();SingleLazy s2 = SingleLazy.getInstance();System.out.println(s1 == s2);}
}
看到了懒汉式的代码.你就可以看出来,跟饿汉式的区别是,懒汉式,就是非必要时候,不创建对象.具体是哪一句呢?我这里给你举出来,你可以思考一下,我说的是不是对的.
public static SingleLazy getInstance(){if (instance == null){instance=new SingleLazy();}return instance;这里加了判断语句,非必要的时候,不创建对象.
当然我们居然说了这种单例模式后,你来思考一下,它是不是线程安全的呢?其实简单来说,它是不是线程安全的,判断还是很简单的,因为你这样看,我们这里有判断条件,有判断条件,就涉及到赋值判断操作,你想象多线程环境下,线程1会对起进行判断,线程2也会进行判断,万一判断不是同时进行的,那不就整了个乌龙时间了吗?另外我们再对照着线程不安全的情况,仔细的思考一下.
所以说,懒汉式会破坏单例模式的原则,导致线程不安全,但是有没有方式解决呢?答案是有的,接下来我就会解释,我们怎么去避免这个问题,所以大家还是不要心急,细细听我道来.
三 懒汉式的单例模式,线程不安全的解决方式
3.1 造成线程不安全的原因
大家不要嫌我啰嗦,我再说明一下原因,这样我们才好说出解决策略
当多个线程同时访问getInstance()方法时,可能会出现竞态条件,即两个线程同时执行了if (instance == null)语句,导致创建了两个实例。这种情况下,线程单例模式就会失效。这个东西,就是我们说的会导致的线程安全问题.
接下来我们来提供一个解决方案.
3.2 解决方案
当然哈,我们开始说了是线程安全的原因,就是在创建对象的那一步,出现了,多线程竞争的关系,那么我们是不是可以直接在创建对象的那一步,加锁.代码如下:
public static SingleLazy getInstance(){synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}return instance;}
当然,我们这一步的操作,其实是保证了,创建对象一开始的原子性.
当然,加锁的操作,我们还是谨慎的,由于加锁的机制,当多个线程同时调用该方法时,只能有一个线程进入临界区创建实例,其他线程则被阻塞,可能会导致性能瓶颈。因此我们要避免就是非必要情况不加锁,就拿当前的这个例子来说,我们首次进入这个代码段的时候,如果对象已经创建了,我们就不进行加锁,对象没有创建,我们就加锁,因此外面就必须再嵌套一层循环.
public static SingletonLazy getInstance() {// 这个条件, 判定是否要加锁. 如果对象已经有了, 就不必加锁了, 此时本身就是线程安全的.if (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}
当然大家不要被这个这俩个if条件所迷惑,一个避免过度加锁,第二个if判断就是避免多线程对其进行同时操作.
到这里,大家觉得我们线程安全的问题结束了吗?其实并没有,因为我们创建对象的时候,还会发生指令重排序.因此我们还必须进行下一步的优化,代码如下所示:
lass SingletonLazy {volatile private static SingletonLazy instance = null;public static SingletonLazy getInstance() {// 这个条件, 判定是否要加锁. 如果对象已经有了, 就不必加锁了, 此时本身就是线程安全的.if (instance == null) {synchronized (SingletonLazy.class) {if (instance == null) {instance = new SingletonLazy();}}}return instance;}
我们来解释一下为什么会出现指令重排序问题,如果你看过我的上一篇线程安全的粗略详解,你就大概明白了,这个问题,当然我们,再来说一下,我们这里为啥会出现现在的问题.
因为创建实例的代码通常会被拆分成多个指令,包括分配内存空间、初始化对象、将对象赋值给变量等。如果编译器或处理器为了提高性能而进行指令重排序,可能会导致上述指令的执行顺序与代码的顺序不一致。这样,如果一个线程在另一个线程完成了对象赋值操作之前执行了if语句的第一个条件判断,就会错误地认为实例已经被创建,从而导致程序出错。
为了解决这个问题,可以使用volatile关键字来修饰单例实例变量,这样可以保证线程对该变量的读写操作都是原子的,并且禁止指令重排序
3.3 总结
最后让我们小小的总结一下,我们解决的办法
1.加锁,把if 和new变成原子操作.
⒉.双重 if,减少不必要的加锁操作
3.使用volatile禁止指令重排序,保证后续线程肯定拿到的是完整对象
相关文章:

线程安全之单例模式
文章目录前言一.什么是单例模式二.在java中的单例模式2.1 饿汉式的介绍2.2 懒汉式的介绍三 懒汉式的单例模式,线程不安全的解决方式3.1 造成线程不安全的原因3.2 解决方案3.3 总结前言 这篇文章,我们会介绍一下单例模式,但这里的单例模式,不是我们所说的设计模式,当然听到设计…...

“二分”带来“十分”快感——二分思想的奥秘解析
文章目录无处不在的二分思想二分查找惊人的查找速度二分查找的递归与非递归实现1.循环退出条件2.mid的取值3.low和high的更新最后说一句🐱🐉作者简介:大家好,我是黑洞晓威,一名大二学生,希望和大家一起进…...
一台服务器最大能支持多少条 TCP 连接?问倒一大片。。。
一台服务器最大能打开的文件数 限制参数 我们知道在Linux中一切皆文件,那么一台服务器最大能打开多少个文件呢?Linux上能打开的最大文件数量受三个参数影响,分别是: fs.file-max (系统级别参数)…...

蓝桥杯嵌入式RTC实时时钟
文章目录 前言一、RTC是什么二、cubemx的配置三、函数的使用总结前言 本篇文章将给大家介绍RTC实时时钟。 一、RTC是什么 STM32的实时时钟RTC是一个独立的定时器,RTC时钟内部依靠BCD码计数。RTC实时时钟提高时钟、闹钟、日历功能。RTC功耗较低,可以使用在低功耗设备上。 …...

Centos7 挂载 ISO镜像
切到mnt目录:cd /mnt mkdir iso确保centos镜像在服务上存在,磁盘挂载mount -o loop /home/xx.iso /mnt/iso查看是否挂载成功df -h出现红色的部分表示挂载成功修改源切目录并修改yum源:cd /etc/yum.repos.dllvim Centos-Base.repo修改后yum clean allyum list安装lrz…...

三级数据库备考--数据库应用系统开发方法第一次练习(刷题库知识点记录)
1.数据库的三级模式由外模式、模式、内模式构成。外模式是用户可见的部分数据的存在形式;模式可以等价为全体数据的逻辑结构且用户不可见,是三级模式的中间部分;内模式对应数据库的物理结构和存储方式。当模式改变时,由数据库管理…...
免费空间主机是什么?怎么申请免费空间主机
随着网络的普及,越来越多的人开始使用免费空间。这种新的商业模式也让一些商家得以获利。 1:免费空间的概念 免费空间是指允许您自由使用的网络服务。这意味着它可以被任何人用来创建、编辑和发布网站内容或应用程序,而无需考虑任何付费业务协…...
网络安全文章汇总导航(持续更新)
网络安全文章汇总导航(持续更新)1.基础篇(已完结):2.工具篇(持续更新):3.靶场安装(持续更新,但不确定):4.权限提升(持续更…...

AI-TestOps —— 软件测试工程师的一把利剑
写在前面软件测试的前世今生测试工具开始盛行AI-TestOps 云平台● AI-TestOps 功能模块● AI-TestOps 自动化测试流程写在前面 最近偶然间看到一句话:“软件测试是整个 IT 行业中最差的岗位”。这顿时激起了我对软件测试领域的兴趣,虽然之前未涉及过软件…...

Linux内核进程管理原理详解
前言:Linux内核里大部分都是C语言。建议先看《Linux内核设计与实现(Linux Kernel Development)》,Robert Love,也就是LKD。Linux是一种动态系统,能够适应不断变化的计算需求。Linux计算需求的表现是以进程的通用抽象为中心的。进程可以是短期…...

通过Linux串口实现树莓派与电脑通信
目录 一 串口说明 二 USB—TTL模块 ● usb-ttl模块接口 三 串口通信常用的API 四 修改串口的配置文件 五 串口通信代码验证 ● 发送一个字符/字符串到串口 ● 树莓读取串口数据(字符) ● 代码拓展(双方) 一 串口…...
全球变暖 蓝桥杯 178
题目描述你有一张某海域 NxN 像素的照片,"."表示海洋、"#"表示陆地,如下所示:........##.....##........##...####....###........其中"上下左右"四个方向上连在一起的一片陆地组成一座岛屿。例如上图就有 2 座…...

Java现在好找工作吗?
Java到2023年已经28岁了,可能你会怀疑它是否还一如当年一样的强大,在应用层领域独占鳌头。但是基于Java庞大的市场占有率和需求,它依然在保持着更新迭代,依然是最常用的底层开发语言,基于其安全性、开放性、稳定性和跨…...

Flink 第1章 基础介绍和特性
一 Flink概念 1.1 Flink的概念 Flink是一个框架和分布式处理引擎,用于对无界和有解数据流进行状态计算。如下图所示: 1.2 Flink的应用场景 1.3 Flink的目标 1.高吞吐量 2.低延迟 3,结果的准确性和良好的容错性。 1.4 Flink与spark的区别…...

docker 安装 nginx无坑版
一. 拉取镜像 docker pull nginx二. 创建挂载目录 mkdir -p /usr/local/nginx/conf mkdir -p /usr/local/nginx/log mkdir -p /usr/local/nginx/html三. 从nginx容器里复制nginx的配置文件到主机里 创建个容器 docker run --name nginx -p 80:80 -d nginx将容器内的配置文件…...

自己动手做chatGPT:向量的概念和相关操作
chatGPT的横空出世给人工智能注入一针强心剂,它是历史上以最短时间达到一亿用户的应用。chatGPT的能力相当惊人,它可以用相当流利的语言和人对话,同时能够对用户提出的问题给出相当顺畅的答案。它的出现已经给各个行业带来不小冲击࿰…...

【洛谷刷题】蓝桥杯专题突破-深度优先搜索-dfs(7)
目录 写在前面: 题目:P1596 [USACO10OCT]Lake Counting S - 洛谷 | 计算机科学教育新生态 (luogu.com.cn) 题目描述: 输入格式: 输出格式: 输入样例: 输出样例: 解题思路: …...

Python嵌套函数(Nested function)和闭包(closure)
Python嵌套函数(Nested function)和闭包(closure) 闭包(closure)是建立在嵌套函数基础上的,是一种特殊的嵌套函数结构。 先看嵌套函数(Nested function)。 Python允许…...

【实战】React 必会第三方插件 —— Cron 表达式生成器(qnn-react-cron)
文章目录一、引子二、配置使用1.安装2.使用(1)直接调用(2)赋值到表单(Form)(3)自定义功能按钮(4)隐藏指定 Tab(5)其他三、常见问题及解…...

C# 教你如何终止Task线程
我们在多线程中通常使用一个bool IsExit类似的代码来控制是否线程的运行与终止,其实使用CancellationTokenSource来进行控制更为好用,下面我们将介绍CancellationTokenSource相关用法。C# 使用 CancellationTokenSource 终止线程使用CancellationTokenSo…...

简易版抽奖活动的设计技术方案
1.前言 本技术方案旨在设计一套完整且可靠的抽奖活动逻辑,确保抽奖活动能够公平、公正、公开地进行,同时满足高并发访问、数据安全存储与高效处理等需求,为用户提供流畅的抽奖体验,助力业务顺利开展。本方案将涵盖抽奖活动的整体架构设计、核心流程逻辑、关键功能实现以及…...

如何在看板中体现优先级变化
在看板中有效体现优先级变化的关键措施包括:采用颜色或标签标识优先级、设置任务排序规则、使用独立的优先级列或泳道、结合自动化规则同步优先级变化、建立定期的优先级审查流程。其中,设置任务排序规则尤其重要,因为它让看板视觉上直观地体…...

dedecms 织梦自定义表单留言增加ajax验证码功能
增加ajax功能模块,用户不点击提交按钮,只要输入框失去焦点,就会提前提示验证码是否正确。 一,模板上增加验证码 <input name"vdcode"id"vdcode" placeholder"请输入验证码" type"text&quo…...

如何将联系人从 iPhone 转移到 Android
从 iPhone 换到 Android 手机时,你可能需要保留重要的数据,例如通讯录。好在,将通讯录从 iPhone 转移到 Android 手机非常简单,你可以从本文中学习 6 种可靠的方法,确保随时保持连接,不错过任何信息。 第 1…...

CocosCreator 之 JavaScript/TypeScript和Java的相互交互
引擎版本: 3.8.1 语言: JavaScript/TypeScript、C、Java 环境:Window 参考:Java原生反射机制 您好,我是鹤九日! 回顾 在上篇文章中:CocosCreator Android项目接入UnityAds 广告SDK。 我们简单讲…...

从零开始打造 OpenSTLinux 6.6 Yocto 系统(基于STM32CubeMX)(九)
设备树移植 和uboot设备树修改的内容同步到kernel将设备树stm32mp157d-stm32mp157daa1-mx.dts复制到内核源码目录下 源码修改及编译 修改arch/arm/boot/dts/st/Makefile,新增设备树编译 stm32mp157f-ev1-m4-examples.dtb \stm32mp157d-stm32mp157daa1-mx.dtb修改…...

C# 类和继承(抽象类)
抽象类 抽象类是指设计为被继承的类。抽象类只能被用作其他类的基类。 不能创建抽象类的实例。抽象类使用abstract修饰符声明。 抽象类可以包含抽象成员或普通的非抽象成员。抽象类的成员可以是抽象成员和普通带 实现的成员的任意组合。抽象类自己可以派生自另一个抽象类。例…...

html-<abbr> 缩写或首字母缩略词
定义与作用 <abbr> 标签用于表示缩写或首字母缩略词,它可以帮助用户更好地理解缩写的含义,尤其是对于那些不熟悉该缩写的用户。 title 属性的内容提供了缩写的详细说明。当用户将鼠标悬停在缩写上时,会显示一个提示框。 示例&#x…...
MySQL账号权限管理指南:安全创建账户与精细授权技巧
在MySQL数据库管理中,合理创建用户账号并分配精确权限是保障数据安全的核心环节。直接使用root账号进行所有操作不仅危险且难以审计操作行为。今天我们来全面解析MySQL账号创建与权限分配的专业方法。 一、为何需要创建独立账号? 最小权限原则…...

NXP S32K146 T-Box 携手 SD NAND(贴片式TF卡):驱动汽车智能革新的黄金组合
在汽车智能化的汹涌浪潮中,车辆不再仅仅是传统的交通工具,而是逐步演变为高度智能的移动终端。这一转变的核心支撑,来自于车内关键技术的深度融合与协同创新。车载远程信息处理盒(T-Box)方案:NXP S32K146 与…...