【JavaEE】多线程案例-单例模式
文章目录
- 1. 前言
- 2. 什么是单例模式
- 3. 如何实现单例模式
- 3.1 饿汉模式
- 3.2 懒汉模式
- 4. 解决单例模式中遇到的线程安全问题
- 4.1 加锁
- 4.2 加上一个判断解决频繁加锁问题
- 4.2 解决因指令重排序造成的线程不安全问题
1. 前言
单例模式是我们面试中最常考到的设计模式。什么是设计模式呢?
设计模式是在计算机科学中,对面向对象设计中反复出现的问题的解决方案的描述。它是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。
设计模式的目的在于可重用代码、让代码更容易被他人理解、提高代码的可靠性。它们通常描述了一组相互紧密作用的类与对象,提供了讨论软件设计的公共语言,使得熟练设计者的设计经验可以被初学者和其他设计者掌握。此外,设计模式还为软件重构提供了目标。
设计模式可以根据目的分为以下三类:
- 创建型模式:主要用于创建对象,这些设计模式提供了一种在创建对象的同时隐藏创建逻辑的方式,而不是使用 new 运算符直接实例化对象。
- 结构型模式:主要用于处理类和对象的组合。
- 行为型模式:主要用于描述类或对象如何交互和怎样分配职责。
此外,根据范围,即模式主要是处理类之间的关系还是处理对象之间的关系,可分为类模式和对象模式两种。
2. 什么是单例模式
单例模式保证一个类在程序中只存咋一个实例,而不会创建出多个实例。就像一个人只能有一个伴侣,而不能有多个伴侣一样。
3. 如何实现单例模式
虽然我们可以自己人为的控制该类只存在一个实例,但是我们人是最不能相信的生物,所以就需要使用计算机来对我们进行约束。当我们想要创建多个实例的时候,就需要编译器做出相应的反应:抛异常或者直接结束进程等。
在Java中实现单例模式可以有两种方式:
- 饿汉模式
- 懒汉模式
3.1 饿汉模式
要想保证某个类只存在一个实例,其中一个很好的方法就是我们在定义这个类的时候就创建一个实例,并且这个实例是唯一的,当出了这个类的时候就不允许再创建该类的实例了。
class Singleton {//定义类的时候就创建一个唯一的实例private static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}
}
因为出了这个类之后不能再创建该类的实例,并且我们需要获得在该类定义时创建的实例,所以可以使用一个静态的 getInstance 方法来获得这个唯一的实例。
虽然我们创建出了这个唯一的实例,但是应该怎样保证出了这个类之后不能再创建实例了呢?
我们都知道,每次创建一个实例的时候,都会调用该类的构造方法(如果你没有实现构造方法,编译器会为你默认创建一个无参数的构造方法),所以我们可以从这个构造方法入手:将构造方法改为私有的构造方法,只有在这个类中创建实例的时候才会创建成功,出了这个类之后,如果再创建第二个实例的时候,因为构造方法是私有的,所以就会创建失败。
class Singleton {private static Singleton instance = new Singleton();public static Singleton getInstance() {return instance;}private Singleton() {}
}
当我们想要创建多个实例的时候,看看会发生什么情况:
当我们在写代码的时候,就会标红报错。然后我们再运行。
所以通过上面的饿汉模式实现单例模式是可以成功的,那么我们再来看看懒汉模式如何实现单例模式。
3.2 懒汉模式
前面的为什么要叫做饿汉模式呢?因为饿汉模式定义类的时候,及创建了一个静态的实例,我们都知道静态的成员变量在类加载的时候就会被创建。这样就会导致不管我们用还是没用到这个实例,这个实例都会被创建,会造成内存和时间的浪费。而我们懒汉模式则很好的解决了这个问题,当定义类的时候,我们先不创建这个实例,而是先定义有这个实例,将这个实例赋值为null,当调用 getInstance 方法的时候,判断这个实例是否为 null,如果是 null 则创建实例,为这个实例申请空间和初始化,如果不为空则直接返回。
class Singleton2 {private static Singleton2 instance = null;public static Singleton2 getInstance() {if(instance == null) {instance = new Singleton2();}return instance;}private Singleton2() {}
}
但是这样就结束了吗?当然不是,既然是多线程的案例,那么我们肯定要考虑到线程的安全问题,那么接下来我们来看看如何解决单例模式中遇到的线程安全问题。
4. 解决单例模式中遇到的线程安全问题
饿汉模式和懒汉模式是否都会在造成线程不安全问题吗?不是的,因为饿汉模式中只有对变量的判断而没有修改操作,但是懒汉模式中当判断 instance 是否为 null 之后,还会对 instance 做出修改,如果线程中存在判断和修改操作的时候,往往会出现线程不安全问题,所以只有懒汉模式会发生线程不安全的问题。
4.1 加锁
为了解决在判断和修改的过程中出现线程不安全的问题,需要在这个过程中进行加锁。
class Singleton2 {private static Singleton2 instance = null;public static Singleton2 getInstance() {synchronized (Singleton2.class) {if(instance == null) {instance = new Singleton2();}}return instance;}private Singleton2() {}
}
虽然我们在这个过程中进行了加锁,但是这个加锁过程并不是每次调用 getInstance 方法的时候都需要进行加锁,如果加锁频繁的话,那么我们这段代码就与高效率无缘了,只有当第一次调用 getInstance 方法的时候才需要加锁,那么我们又该如何优化这个频繁加锁问题呢?
4.2 加上一个判断解决频繁加锁问题
class Singleton2 {private static Singleton2 instance = null;public static Singleton2 getInstance() {if(instance == null) {synchronized (Singleton2.class) {if(instance == null) {instance = new Singleton2();}}}return instance;}private Singleton2() {}
}
当再加上一个判断的时候,可能会有人问了,我为了创建一个实例使用了两个相同的判断,那么这个判断不显得多余吗?不多于,这两个判断完全不多余。
- 第一个判断是判断是否需要加锁,避免频繁加锁
- 第二个判断是为了判断是否需要创建实例
当实例已经不为 null 的时候,那么因为第一个判断,就不会进行加锁,而是直接返回 instance。
4.2 解决因指令重排序造成的线程不安全问题
只有上面的两个优化是不够的,我们都知道造成线程不安全的问题还有指令重排序的问题。可以将创建实例的过程细分为三个步骤:
- 向内存申请空间
- 调用构造方法对该内存进行初始化
- 将该内存赋值给 instance
如果在创建实例的过程中发生了指令重排序,线程 t1 执行的本应该的顺序为1、2、3,但是却重排序成了1、3、2,那么当线程 t2 和线程 t1 并发执行的时候,就会将没有初始化的引用给返回,从而会出现比较严重的后果。
所以为了解决指令重排序而发生的线程不安全问题,我们需要使用 volatile 来保证内存的可见性,防止出现指令重排序的发生。
class Singleton2 {private volatile static Singleton2 instance = null;public static Singleton2 getInstance() {if(instance == null) {synchronized (Singleton2.class) {if(instance == null) {instance = new Singleton2();}}}return instance;}private Singleton2() {}
}
有了这三个优化,才真正保证了单例模式的安全进行。
相关文章:

【JavaEE】多线程案例-单例模式
文章目录 1. 前言2. 什么是单例模式3. 如何实现单例模式3.1 饿汉模式3.2 懒汉模式4. 解决单例模式中遇到的线程安全问题4.1 加锁4.2 加上一个判断解决频繁加锁问题4.2 解决因指令重排序造成的线程不安全问题 1. 前言 单例模式是我们面试中最常考到的设计模式。什么是设计模式呢…...

社区分享|MeterSphere变身“啄木鸟”,助力云帐房落地接口自动化测试
云帐房网络科技有限公司(以下简称为“云帐房”)成立于2015年3月,以“成为最值得信赖的税务智能公司”为愿景,运用人工智能、大数据等互联网技术,结合深厚的财税行业服务经验,为代账公司和中大型企业提供智能…...

fpga内嵌逻辑分析仪使用方法
文章目录 前言一、方法1 — 使用 IP 核创建 ILA 调试环境1、创建 ILA ip 核2、进行例化3、生成比特流文件4、下载程序5、进行在线调试 二、方法2 — 使用 Debug 标记创建 ILA1、Debug 标记相关信号2、综合操作3、设置 Set Up Debug4、生成比特文件5、下载程序6、进行在线调试 前…...

第14章 结构和其他数据形式
本章介绍以下内容: 关键字:struct、union、typedef 运算符:.、-> 什么是C结构,如何创建结构模板和结构变量 如何访问结构的成员,如何编写处理结构的函数 联合和指向函数的指针 设计程序时,最重要的步骤之…...

vue 把echarts封装成一个方法 并且从后端读取数据 +转换数据格式 =动态echarts 联动echarts表
1.把echarts 在 methods 封装成一个方法mounted 在中调用 折线图 和柱状图 mounted调用下边两个方法 mounted(){//最早获取DOM元素的生命周期函数 挂载完毕console.log(mounted-id , document.getElementById(charts))this.line()this.pie()},methods里边的方法 line() {// …...
Python基础08 面向对象的基本概念
Python使用类(class)和对象(object),进行面向对象(object-oriented programming,简称OOP)的编程。 面向对象的最主要目的是提高程序的重复使用性。我们这么早切入面向对象编程的原因是,Python的整个概念是基于对象的。…...

APP自动化之Poco框架
今天给大家介绍一款自动化测试框架Poco,其脚本写法非常简洁、高效,其元素定位器效率更快,其本质基于python的第三方库,调试起来也会非常方便,能够很好的提升自动化测试效率,节省时间。 (一)背景…...
c++拷贝构造【显式调用】和运算符=重载构造【隐式调用】解析
深拷贝 vs. 浅拷贝 深拷贝:开辟新内存,独立对象,堆区浅拷贝:共享内存,引用对象,栈区 深拷贝:深拷贝是一种拷贝方式,它会在堆区重新分配内存并复制对象的内容。 这意味着原对象和新…...

无涯教程-JavaScript - LCM函数
描述 LCM函数返回整数的最小公倍数。最小公倍数是最小的正整数,它是所有整数参数number1,number2等的倍数。使用LCM添加具有不同分母的分数。 语法 LCM (number1, [number2] ...)争论 Argument描述Required/OptionalNumber1, number2... 您想要最小公倍数的1到255个值。 如…...

Java多线程篇(3)——线程池
文章目录 线程池ThreadPoolExecutor源码分析1、如何提交任务2、如何执行任务3、如何停止过期的非核心线程4、如何使用拒绝策略 ScheduledThreadPoolExecutor源码分析 线程池 快速过一遍基础知识 7大参数 corePoolSize : 核心线程数 maximumPoolSize: 最…...
那些年我们遇到过的关于excel的操作
本文为直接从百度上搜索的关于excel的函数使用,方便以后用,希望会持续补充 excel中筛选出两列重复的数据【场景:A、B两列数据个数不同且无序,想找出A列中的数据在B列中不存在的,通过比较后单元格为空的代表该行不存在的…...

Angular变更检测机制
前段时间遇到这样一个 bug,通过一个 click 事件跳转到一个新页面,新页面迟迟不加载; 经过多次测试发现,将鼠标移入某个 tab ,页面就加载出来了。 举个例子,页面内容无法加载,但是将鼠标移入下图…...

Redis之String类型
文章目录 Redis之String类型1. 赋值/获取值2. 同时设置/获取多个键值3. 数值增减4. 获取字符串长度5. 向尾部追加值6. 分布式锁7.应用场景 Redis之String类型 Redis命令不区分大小写 1. 赋值/获取值 赋值:set key value 取值:get key (当键不存在时候&…...
使用redis中的zset实现滑动窗口限流
使用redis和zset实现滑动窗口限流 文章目录 使用redis和zset实现滑动窗口限流Zset**初始化一个ZSet**:其中包含所有用户的ID和时间戳。**添加元素到ZSet**:当用户发起请求时,将当前时间戳和用户ID作为元素添加到ZSet中。**删除过期的元素**&a…...

Linux下C语言使用 netlink sockets与内核模块通信
netlink简介 Netlink套接字是用以实现用户进程与内核进程通信的一种特殊的进程间通信(IPC) ,也是网络应用程序与内核通信的最常用的接口。在Linux标准内核中,系统默认集成了很多netlink实例,比如日志上报、路由系统等,netlink消息是双向的&a…...

excel中的引用与查找函数篇3
1、INDEX(array,row_num,[col_num]):获取指定范围中指定行号和列号对应的数据 index(查询范围,行号,列号) 行号和列号是相对选中查询范围来写的:分别把第二行第三列的数据和第四行第二列的数据查找出来。 数据是单行或单列,后面只需要给一个参…...

【Linux学习笔记】 - 常用指令学习及其验证(下)
前言:本文延续上一篇文章【Linux学习笔记】 - 常用指令学习及其验证(上)对常用的指令进行介绍和验证。 一、mv指令 (1)功能:用来移动文件或者将文件改名 (2)语法及验证:…...

面试官:请说说flex布局_番茄出品.md
面试官:请说说flex布局_番茄出品.md start 依然记得当初学习 flex 布局时,用 flex 布局:画麻将。一筒到九筒,应有尽有。但是光和面试官说,我用 flex 布局画过麻将,并没有什么用。面试官问你一个语法&…...
ChatGLM DeepSpeed/P-Tuning v2 调参
之前尝试了基于ChatGLM-6B使用LoRA进行参数高效微调,本文给大家分享使用DeepSpeed和P-Tuning v2对ChatGLM-6B进行微调,相关代码放置在GitHub上面:llm-action。 ChatGLM-6B简介 ChatGLM-6B相关的简介请查看之前的文章,这里不再赘述。 P-Tuning v2简介 P-Tuning是一种较新…...

Leetcode每日一题:打家劫舍系列Ⅰ、Ⅱ、Ⅲ、Ⅳ(2023.9.16~2023.9.19 C++)
由于之前写过打家劫舍系列,这里直接弄个合集,后面应该还有个iv。 目录 198. 打家劫舍 213. 打家劫舍 II 337. 打家劫舍 III 2560. 打家劫舍 IV 198. 打家劫舍 题目描述: 你是一个专业的小偷,计划偷窃沿街的房屋。每间房内都…...
后进先出(LIFO)详解
LIFO 是 Last In, First Out 的缩写,中文译为后进先出。这是一种数据结构的工作原则,类似于一摞盘子或一叠书本: 最后放进去的元素最先出来 -想象往筒状容器里放盘子: (1)你放进的最后一个盘子(…...
内存分配函数malloc kmalloc vmalloc
内存分配函数malloc kmalloc vmalloc malloc实现步骤: 1)请求大小调整:首先,malloc 需要调整用户请求的大小,以适应内部数据结构(例如,可能需要存储额外的元数据)。通常,这包括对齐调整,确保分配的内存地址满足特定硬件要求(如对齐到8字节或16字节边界)。 2)空闲…...

练习(含atoi的模拟实现,自定义类型等练习)
一、结构体大小的计算及位段 (结构体大小计算及位段 详解请看:自定义类型:结构体进阶-CSDN博客) 1.在32位系统环境,编译选项为4字节对齐,那么sizeof(A)和sizeof(B)是多少? #pragma pack(4)st…...

遍历 Map 类型集合的方法汇总
1 方法一 先用方法 keySet() 获取集合中的所有键。再通过 gey(key) 方法用对应键获取值 import java.util.HashMap; import java.util.Set;public class Test {public static void main(String[] args) {HashMap hashMap new HashMap();hashMap.put("语文",99);has…...
Java 加密常用的各种算法及其选择
在数字化时代,数据安全至关重要,Java 作为广泛应用的编程语言,提供了丰富的加密算法来保障数据的保密性、完整性和真实性。了解这些常用加密算法及其适用场景,有助于开发者在不同的业务需求中做出正确的选择。 一、对称加密算法…...
HTML前端开发:JavaScript 常用事件详解
作为前端开发的核心,JavaScript 事件是用户与网页交互的基础。以下是常见事件的详细说明和用法示例: 1. onclick - 点击事件 当元素被单击时触发(左键点击) button.onclick function() {alert("按钮被点击了!&…...

让AI看见世界:MCP协议与服务器的工作原理
让AI看见世界:MCP协议与服务器的工作原理 MCP(Model Context Protocol)是一种创新的通信协议,旨在让大型语言模型能够安全、高效地与外部资源进行交互。在AI技术快速发展的今天,MCP正成为连接AI与现实世界的重要桥梁。…...

项目部署到Linux上时遇到的错误(Redis,MySQL,无法正确连接,地址占用问题)
Redis无法正确连接 在运行jar包时出现了这样的错误 查询得知问题核心在于Redis连接失败,具体原因是客户端发送了密码认证请求,但Redis服务器未设置密码 1.为Redis设置密码(匹配客户端配置) 步骤: 1).修…...
鸿蒙DevEco Studio HarmonyOS 5跑酷小游戏实现指南
1. 项目概述 本跑酷小游戏基于鸿蒙HarmonyOS 5开发,使用DevEco Studio作为开发工具,采用Java语言实现,包含角色控制、障碍物生成和分数计算系统。 2. 项目结构 /src/main/java/com/example/runner/├── MainAbilitySlice.java // 主界…...
在Ubuntu24上采用Wine打开SourceInsight
1. 安装wine sudo apt install wine 2. 安装32位库支持,SourceInsight是32位程序 sudo dpkg --add-architecture i386 sudo apt update sudo apt install wine32:i386 3. 验证安装 wine --version 4. 安装必要的字体和库(解决显示问题) sudo apt install fonts-wqy…...