当前位置: 首页 > news >正文

多线程下的单例设计模式(新手必看!!!)

在项目中为了避免创建大量的对象,频繁出现gc的问题,单例设计模式闪亮登场。

一、饿汉式

1.1饿汉式

顾名思义就是我们比较饿,每次想吃的时候,都提前为我们创建好。其实我记了好久也没分清楚饿汉式和懒汉式的区别。这里给出我的一个记忆方法:懒汉式就是懒加载,什么是懒加载呢?就是我们需要的时候给创建对象就行,稍后介绍懒汉式的时候你会发现这个现象。

1.2饿汉式的特点

线程安全,但是如果一个项目需要创建大量的对象的时候,当项目运行的时候,会创建大量我们暂时用不到的对象。

1.3饿汉式代码
package singletonModel;
public class HungrySingleton {public static HungrySingleton instance=new HungrySingleton();private HungrySingleton(){}public static HungrySingleton getInstance(){return instance;}
}
1.4多线程下测试
package Test;
import singletonModel.DoubleLockSingleton;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
public class SingletonTest {public static void main(String[] args) {// 使用AtomicReference来存储第一次获取到的LazySingleton实例AtomicReference<DoubleLockSingleton> singletonInstance = new AtomicReference<>();// 我们将启动大量线程来尝试突破单例的线程安全性ExecutorService executorService = Executors.newFixedThreadPool(100);// 用于发现多个实例创建的标志AtomicReference<Boolean> flag = new AtomicReference<>(false);// 提交多个任务到线程池,尝试并发地获取单例实例for (int i = 0; i < 100; i++) {executorService.submit(() -> {DoubleLockSingleton instance = DoubleLockSingleton.getInstance();// 如果原子引用为空,我们设置当前实例if (singletonInstance.get() == null) {singletonInstance.set(instance);} else if (singletonInstance.get() != instance) {// 如果原子引用中的实例与当前获取的实例不同,说明存在多个实例flag.set(true);System.out.println("Detected multiple instances!");}});}executorService.shutdown();// 等待所有任务完成while (!executorService.isTerminated()) {// 等待所有线程执行完毕}if (flag.get().equals(false)) {System.out.println("No multiple instances detected!");}}
}
1.5运行结果

在这里插入图片描述

通过实验证明,饿汉式在多线程环境下是线程安全的!

二、懒汉式

2.1懒汉式

顾名思义比较懒,叫我们的时候,我们在穿衣服去干活,即完成对象的创建的过程。

2.2懒汉式的特点

需要的时候,才为我们创建,能够避免在项目启动的时候,创建大量的无用对象,减少GC。缺点就是多线程操作下线程不安全!

2.3懒汉式代码
package singletonModel;
public class LazySingleton {private static LazySingleton lazyInstance;private LazySingleton(){}public static LazySingleton getInstance(){if(lazyInstance==null){lazyInstance= new LazySingleton();}return lazyInstance;}
}
2.4多线程下测试
package Test;
import singletonModel.LazySingleton;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
public class SingletonTest {public static void main(String[] args) {// 使用AtomicReference来存储第一次获取到的LazySingleton实例AtomicReference<LazySingleton> singletonInstance = new AtomicReference<>();// 我们将启动大量线程来尝试突破单例的线程安全性ExecutorService executorService = Executors.newFixedThreadPool(100);// 用于发现多个实例创建的标志AtomicReference<Boolean> flag = new AtomicReference<>(false);// 提交多个任务到线程池,尝试并发地获取单例实例for (int i = 0; i < 100; i++) {executorService.submit(() -> {LazySingleton instance = LazySingleton.getInstance();// 如果原子引用为空,我们设置当前实例if (singletonInstance.get() == null) {singletonInstance.set(instance);} else if (singletonInstance.get() != instance) {// 如果原子引用中的实例与当前获取的实例不同,说明存在多个实例flag.set(true);System.out.println("Detected multiple instances!");}});}executorService.shutdown();// 等待所有任务完成while (!executorService.isTerminated()) {// 等待所有线程执行完毕}if (flag.get().equals(false)) {System.out.println("No multiple instances detected!");}}
}

上述代码需要多次测试,就能够测试出线程不安全的!

2.5测试结果

在这里插入图片描述
测试证明懒汉式在多线程操作下是线程不安全的!

2.6具体原因

具体的原因就是发生在下图的位置:即多线程环境下,不知线程哪个执行快慢,即存在两个线程A,B,线程A在进入if语句的时候,判断为空,然后完成对象的创建,但是对象的创建也需要一定时间,这个时候线程B也进入if判断,当前线程A还没有创建好,则判断为null,同时也完成对象的创建,这时候线程A,B创建的对象就不是同一个对象了。也就是线程不安全的了,即不满足原子性,可见性,有序性。
在这里插入图片描述

三、懒汉式方案修补方案一

为了保证线程安全,即满足原子性,可见性,有序性。我们首先想到的就是加锁!

由于getInstance方法为static修饰的方式,我们加了synchronized后,锁住的是当前的类,即加的类锁。即多线程操作该类的时候,只有1个线程操作成功!

3.1代码
package singletonModel;public class RLazySingleton {static RLazySingleton instance;private RLazySingleton(){}synchronized public static RLazySingleton getInstance(){if(instance==null){instance=new RLazySingleton();}return instance;}
}
3.2多线程测试代码
package Test;
import singletonModel.RLazySingleton;import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;public class SingletonTest {public static void main(String[] args) {// 使用AtomicReference来存储第一次获取到的LazySingleton实例AtomicReference<RLazySingleton> singletonInstance = new AtomicReference<>();// 我们将启动大量线程来尝试突破单例的线程安全性ExecutorService executorService = Executors.newFixedThreadPool(100);// 用于发现多个实例创建的标志AtomicReference<Boolean> flag = new AtomicReference<>(false);// 提交多个任务到线程池,尝试并发地获取单例实例for (int i = 0; i < 100; i++) {executorService.submit(() -> {RLazySingleton instance = RLazySingleton.getInstance();// 如果原子引用为空,我们设置当前实例if (singletonInstance.get() == null) {singletonInstance.set(instance);} else if (singletonInstance.get() != instance) {// 如果原子引用中的实例与当前获取的实例不同,说明存在多个实例flag.set(true);System.out.println("Detected multiple instances!");}});}executorService.shutdown();// 等待所有任务完成while (!executorService.isTerminated()) {// 等待所有线程执行完毕}if (flag.get().equals(false)) {System.out.println("No multiple instances detected!");}}
}
3.3测试结果

在这里插入图片描述

实验结果证明:这种测试代码也是线程安全的!

3.4存在的问题

通过在getInstance()方法上添加synchronized关键字,可以强制每次只有一个线程能够访问方法,从而避免竞态条件。但这样做会影响性能,因为每次访问都需要进行同步。

四、双重锁检测方案

解决每次访问都需要进行同步的问题。

4.1代码
package singletonModel;
public class DoubleLockSingleton {private static DoubleLockSingleton instance;private DoubleLockSingleton(){}public  static  DoubleLockSingleton getInstance(){if(instance==null){synchronized (DoubleLockSingleton.class){if(instance==null){instance=new DoubleLockSingleton();}}}return instance;}
}
4.2测试代码
package Test;
import singletonModel.DoubleLockSingleton;
import singletonModel.RLazySingleton;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicReference;
public class SingletonTest {public static void main(String[] args) {// 使用AtomicReference来存储第一次获取到的LazySingleton实例AtomicReference<RLazySingleton> singletonInstance = new AtomicReference<>();// 我们将启动大量线程来尝试突破单例的线程安全性ExecutorService executorService = Executors.newFixedThreadPool(100);// 用于发现多个实例创建的标志AtomicReference<Boolean> flag = new AtomicReference<>(false);// 提交多个任务到线程池,尝试并发地获取单例实例for (int i = 0; i < 100; i++) {executorService.submit(() -> {RLazySingleton instance = RLazySingleton.getInstance();// 如果原子引用为空,我们设置当前实例if (singletonInstance.get() == null) {singletonInstance.set(instance);} else if (singletonInstance.get() != instance) {// 如果原子引用中的实例与当前获取的实例不同,说明存在多个实例flag.set(true);System.out.println("Detected multiple instances!");}});}executorService.shutdown();// 等待所有任务完成while (!executorService.isTerminated()) {// 等待所有线程执行完毕}if (flag.get().equals(false)) {System.out.println("No multiple instances detected!");}}
}
测试结果

实验结果也是线程安全的。
在这里插入图片描述

五、其他线程安全的写法

5.1静态内部类
public class StaticInnerClassSingleton {private static class LazyHolder {private static final StaticInnerClass INSTANCE = new StaticInnerClass();}private StaticInnerClass(){}public static StaticInnerClass getInstance(){return LazyHolder.INSTANCE;}
}
5.2枚举类
package singletonModel;public enum EnumSingleton {Instance;public void getInstance(){System.out.println("枚举类创建对象");}
}

六、总结

在Java中,使用枚举(enum)实现的单例模式是唯一能够抵御反射攻击的方式,因为枚举类型没有构造方法(在字节码层面是有私有构造器的,但这是由编译器自己添加的),所以无法通过反射来实例化枚举类型。
枚举攻击!!!

import java.lang.reflect.Constructor;public class ReflectionSingletonAttack {public static void main(String[] args) {Singleton instanceOne = Singleton.getInstance();Singleton instanceTwo = null;try {// 获取Singleton类的构造函数Constructor[] constructors = Singleton.class.getDeclaredConstructors();for (Constructor constructor : constructors) {// 设置构造函数的访问权限为可访问constructor.setAccessible(true);// 使用构造函数创建一个新的Singleton实例instanceTwo = (Singleton) constructor.newInstance();break;}} catch (Exception e) {e.printStackTrace();}// 打印两个实例的哈希码System.out.println("Instance 1 hash:" + instanceOne.hashCode());System.out.println("Instance 2 hash:" + instanceTwo.hashCode());}
}

枚举类单例模式抵挡枚举攻击

import java.lang.reflect.Constructor;public class EnumReflectionAttack {public static void main(String[] args) {EnumSingleton instanceOne = EnumSingleton.INSTANCE;EnumSingleton instanceTwo = null;try {Constructor[] constructors = EnumSingleton.class.getDeclaredConstructors();for (Constructor constructor : constructors) {constructor.setAccessible(true);instanceTwo = (EnumSingleton) constructor.newInstance();break;}} catch (Exception e) {e.printStackTrace();}System.out.println("Instance 1 hash:" + instanceOne.hashCode());System.out.println("Instance 2 hash:" + (instanceTwo != null ? instanceTwo.hashCode() : "instance creation failed"));}
}

在运行此代码时,您会收到类似以下的异常:

java.lang.IllegalArgumentException: Cannot reflectively create enum objects

因此,使用枚举的方式创建单例是安全的,它有效地防止了反射攻击以及解决了序列化问题。这也是为什么很多推荐使用枚举方式来实现单例模式的原因之一。

相关文章:

多线程下的单例设计模式(新手必看!!!)

在项目中为了避免创建大量的对象&#xff0c;频繁出现gc的问题&#xff0c;单例设计模式闪亮登场。 一、饿汉式 1.1饿汉式 顾名思义就是我们比较饿&#xff0c;每次想吃的时候&#xff0c;都提前为我们创建好。其实我记了好久也没分清楚饿汉式和懒汉式的区别。这里给出我的一…...

JDK 21的新特性总结和分析

&#x1f337;&#x1f341; 博主猫头虎 带您 Go to New World.✨&#x1f341; &#x1f984; 博客首页——猫头虎的博客&#x1f390; &#x1f433;《面试题大全专栏》 文章图文并茂&#x1f995;生动形象&#x1f996;简单易学&#xff01;欢迎大家来踩踩~&#x1f33a; &a…...

【VR】【Unity】白马VR课堂系列-VR开发核心基础03-项目准备-VR项目设置

【内容】 详细说明 在设置Camera Rig前,我们需要针对VR游戏做一些特别的Project设置。 点击Edit菜单,Project Settings,选中最下方的XR Plugin Management,在右边面板点击Install。 安装完成后,我们需要选中相应安卓平台下的Pico VR套件,关于怎么安装PICO VR插件,请参…...

Windows服务器安装php+mysql环境的经验分享

php mysql环境 下载IIS Php Mysql环境集成包,集成包下载地址: 1、Windows Server 2008 一键安装Web环境包 x64 适用64位操作系统服务器:下载地址:链接: https://pan.baidu.com/s/1MMOOLGll4D7Eb5tBrdTQZw 提取码: btnx 2、Windows Server 2008 一键安装Web环境包 32 适…...

【LeetCode热题100】--287.寻找重复数

287.寻找重复数 方法&#xff1a;使用快慢指针 使用环形链表II的方法解题&#xff08;142.环形链表II&#xff09;&#xff0c;使用 142 题的思想来解决此题的关键是要理解如何将输入的数组看作为链表。 首先明确前提&#xff0c;整数的数组 nums 中的数字范围是 [1,n]。考虑一…...

JUC并发编程——Stream流式计算(基于狂神说的学习笔记)

Stream流式计算 什么是Stream流式计算 Stream流式计算是一种基于数据流的计算模式&#xff0c;它可以对数据进行实时处理和分析&#xff0c;而不需要将所有数据存储在内存中。 Stream流式计算是将数据源中的数据分割成多个小的数据块&#xff0c;然后对每个小的数据块进行并…...

【Eclipse】取消按空格自动补全,以及出现没有src的解决办法

【Eclipse】设置自动提示 教程 根据上方链接&#xff0c;我们已经知道如何设置Eclipse的自动补全功能了&#xff0c;但是有时候敲变量名的时候按空格&#xff0c;本意是操作习惯&#xff0c;不需要自动补全&#xff0c;但是它却给我们自动补全了&#xff0c;这就造成了困扰&…...

ps制作透明公章 公章变透明 ps自动化批量抠图制作透明公章

ps制作透明公章 公章变透明 ps自动化批量抠图制作透明公章 1、抠图制作透明公章2、ps自动化批量抠图制作透明公章 1、抠图制作透明公章 抠图过程看视频 直接访问视频连接可以选高清画质 https://live.csdn.net/v/335752 ps抠图制作透明公章 2、ps自动化批量抠图制作透明公章 …...

Fetch与Axios数据请求

什么是Polyfill? Polyfill是一个js库&#xff0c;主要抚平不同浏览器之间对js实现的差异。比如&#xff0c;html5的storage(session,local), 不同浏览器&#xff0c;不同版本&#xff0c;有些支持&#xff0c;有些不支持。Polyfill&#xff08;Polyfill有很多&#xff0c;在Gi…...

论文阅读-FCD-Net: 学习检测多类型同源深度伪造人脸图像

一、论文信息 论文题目&#xff1a;FCD-Net: Learning to Detect Multiple Types of Homologous Deepfake Face Images 作者团队&#xff1a;Ruidong Han , Xiaofeng Wang , Ningning Bai, Qin Wang, Zinian Liu, and Jianru Xue &#xff08;西安理工大学&#xff0c;西安交…...

云服务器快速搭建网站

目录 安装Apache Docker 安装 Mysql 安装 Docker 依赖包 添加 Docker 官方仓库 安装 Docker 引擎 启动 Docker 服务并设置开机自启 验证 Docker 是否成功安装 拉取 MySQL 镜像 查看本地镜像 运行容器 停止和启动容器 列出正在运行的容器 安装PHP环境 搭建网站 安装…...

小程序首页搭建

小程序首页搭建 1. Flex布局是什么&#xff1f;2. 容器的属性2.1 flex-direction属性2.2 flex-wrap属性2.3 flex-flow属性2.4 justify-content属性2.5 align-items属性2.6 align-content属性 二.首页布局搭建二.1moke模拟数据实现轮播图4.信息搭建 Flex弹性布局 1. Flex布局是…...

5、使用 pgAdmin4 图形化创建和管理 PostgreSQL 数据库

通过上几篇文章我们讲解了如何安装 PostgreSQL 数据库软件和 pgAdmin4 图形化管理工具。 今天我们继续学习如何通过 pgAdmin4 管理工具图形化创建和管理 PostgreSQL 数据库。 一、PostgreSQL的基本工作方式 在学习如何使用PostgreSQL创建数据库之前&#xff0c;我们需要了解一…...

EtherCAT转Modbus-TCP协议网关与DCS连接的配置方法

远创智控YC-ECTM-TCP&#xff0c;自主研发的通讯网关&#xff0c;将为你解决以太网通讯难题。YC-ECTM-TCP是一款EtherCAT主站功能的通讯网关&#xff0c;能够将EtherCAT网络和Modbus-TCP网络连接起来。它可以作为EtherCAT网络中的主站使用&#xff0c;同时也可以作为Modbus-TCP…...

合伙企业的执行事务合伙人委派代表是什么样的存在

当合伙企业的执行事务合伙人为法人或非法人组织时&#xff0c;通常会委派自然人代表其执行合伙事务&#xff0c;特别是各类投资基金、信托、资产证券化等合伙企业类型的SPV中&#xff0c;由法人执行事务合伙人委派代表执行合伙企业事务比较常见&#xff0c;由此可能出现合伙企业…...

visual studio设置主题和背景颜色

visual studio2019默认的主题有4种&#xff0c;分别是浅白色、深黑色、蓝色、蓝(额外对比度)&#xff0c;背景颜色默认是纯白色RGB(255,255,255)。字体纯白色看久了&#xff0c;眼睛会感到酸痛、疲劳&#xff0c;建议改成浅白RGB(250,250,250)、豆沙绿RGB(85,123,105)、透明蓝白…...

[JVM]问下,对象在堆上的内存分配是怎样的

Java 技术体系的自动内存管理,最根本的目标是自动化地解决两个问题:自动给对象分配内存以及自动回收分配给对象的内存 这里面最重要的就是,对象在堆上的内存分配 这篇文章来具体讲讲 堆整体上来说,主要分为 新生代 & 老年代 新生代又分为: Eden 区和 Survivor 区, Survivo…...

TCP/IP网络分层模型

TCP/IP当初的设计者真的是非常聪明&#xff0c;创造性地提出了“分层”的概念&#xff0c;把复杂的网络通信划分出多个层次&#xff0c;再给每一个层次分配不同的职责&#xff0c;层次内只专心做自己的事情就好&#xff0c;用“分而治之”的思想把一个“大麻烦”拆分成了数个“…...

数据结构-----红黑树的插入

目录 前言 红黑树的储存结构 一、节点旋转操作 左旋&#xff08;Left Rotation&#xff09; 右旋&#xff08;Right Rotation&#xff09; 二、插入节点 1.插入的是空树 2.插入节点的key重新重复 3.插入节点的父节点是黑色 4.插入节点的父节点是红色 4.1父节点是祖父…...

Excel大量表格选择,快速定位表格

excel有大量表格&#xff0c;快速定位表格方法。 在这个区域电机鼠标右键 出现表格选择。&#xff08;此处方便查看15个表格&#xff09;&#xff0c;如果超过15个表格可以选择其他工资表。 选择其他工作表会弹出列表框如下图 特此记录 anlog 2023年10月12日...

《Playwright:微软的自动化测试工具详解》

Playwright 简介:声明内容来自网络&#xff0c;将内容拼接整理出来的文档 Playwright 是微软开发的自动化测试工具&#xff0c;支持 Chrome、Firefox、Safari 等主流浏览器&#xff0c;提供多语言 API&#xff08;Python、JavaScript、Java、.NET&#xff09;。它的特点包括&a…...

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility

Cilium动手实验室: 精通之旅---20.Isovalent Enterprise for Cilium: Zero Trust Visibility 1. 实验室环境1.1 实验室环境1.2 小测试 2. The Endor System2.1 部署应用2.2 检查现有策略 3. Cilium 策略实体3.1 创建 allow-all 网络策略3.2 在 Hubble CLI 中验证网络策略源3.3 …...

python爬虫:Newspaper3k 的详细使用(好用的新闻网站文章抓取和解析的Python库)

更多内容请见: 爬虫和逆向教程-专栏介绍和目录 文章目录 一、Newspaper3k 概述1.1 Newspaper3k 介绍1.2 主要功能1.3 典型应用场景1.4 安装二、基本用法2.2 提取单篇文章的内容2.2 处理多篇文档三、高级选项3.1 自定义配置3.2 分析文章情感四、实战案例4.1 构建新闻摘要聚合器…...

ETLCloud可能遇到的问题有哪些?常见坑位解析

数据集成平台ETLCloud&#xff0c;主要用于支持数据的抽取&#xff08;Extract&#xff09;、转换&#xff08;Transform&#xff09;和加载&#xff08;Load&#xff09;过程。提供了一个简洁直观的界面&#xff0c;以便用户可以在不同的数据源之间轻松地进行数据迁移和转换。…...

OpenLayers 分屏对比(地图联动)

注&#xff1a;当前使用的是 ol 5.3.0 版本&#xff0c;天地图使用的key请到天地图官网申请&#xff0c;并替换为自己的key 地图分屏对比在WebGIS开发中是很常见的功能&#xff0c;和卷帘图层不一样的是&#xff0c;分屏对比是在各个地图中添加相同或者不同的图层进行对比查看。…...

iOS性能调优实战:借助克魔(KeyMob)与常用工具深度洞察App瓶颈

在日常iOS开发过程中&#xff0c;性能问题往往是最令人头疼的一类Bug。尤其是在App上线前的压测阶段或是处理用户反馈的高发期&#xff0c;开发者往往需要面对卡顿、崩溃、能耗异常、日志混乱等一系列问题。这些问题表面上看似偶发&#xff0c;但背后往往隐藏着系统资源调度不当…...

在鸿蒙HarmonyOS 5中使用DevEco Studio实现企业微信功能

1. 开发环境准备 ​​安装DevEco Studio 3.1​​&#xff1a; 从华为开发者官网下载最新版DevEco Studio安装HarmonyOS 5.0 SDK ​​项目配置​​&#xff1a; // module.json5 {"module": {"requestPermissions": [{"name": "ohos.permis…...

API网关Kong的鉴权与限流:高并发场景下的核心实践

&#x1f525;「炎码工坊」技术弹药已装填&#xff01; 点击关注 → 解锁工业级干货【工具实测|项目避坑|源码燃烧指南】 引言 在微服务架构中&#xff0c;API网关承担着流量调度、安全防护和协议转换的核心职责。作为云原生时代的代表性网关&#xff0c;Kong凭借其插件化架构…...

在golang中如何将已安装的依赖降级处理,比如:将 go-ansible/v2@v2.2.0 更换为 go-ansible/@v1.1.7

在 Go 项目中降级 go-ansible 从 v2.2.0 到 v1.1.7 具体步骤&#xff1a; 第一步&#xff1a; 修改 go.mod 文件 // 原 v2 版本声明 require github.com/apenella/go-ansible/v2 v2.2.0 替换为&#xff1a; // 改为 v…...

java高级——高阶函数、如何定义一个函数式接口类似stream流的filter

java高级——高阶函数、stream流 前情提要文章介绍一、函数伊始1.1 合格的函数1.2 有形的函数2. 函数对象2.1 函数对象——行为参数化2.2 函数对象——延迟执行 二、 函数编程语法1. 函数对象表现形式1.1 Lambda表达式1.2 方法引用&#xff08;Math::max&#xff09; 2 函数接口…...