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

news2025/7/13 1:30:08

在项目中为了避免创建大量的对象,频繁出现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

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

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

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

相关文章

MyCat 2全套学习笔记(完整配置【主从+集群】+理论解析 + 大厂真实业务理解)

目录 入门概述 MyCat概念 MyCat的作用 读写分离 数据分片 多数据源整合 MyCat 解决问题的思路 MyCat 和MySQL的区别 MyCat原理 MyCAT2的安装 前言 下载压缩包和jar包 安装MyCAT2 创建/data/tools 进入/data/tools目录 下载 下载完成 解压并移动到data目录下 修改权限 把所需…

【工具】利用ffmpeg将网页中的.m3u8视频文件转化为.mp4格式

目录 0.环境 1.背景 2.前提 3.详细描述 1&#xff09;在网站上找到你想下载的视频的.m3u8链接 2&#xff09;打开命令行&#xff0c;用ffmpeg命令进行转化 3&#xff09;过程&结果截图 0.环境 windows64 ffmpeg 1.背景 网页上有个.m3u8格式的视频文件&#xff0c;…

ZKP3.2 Programming ZKPs (Arkworks Zokrates)

ZKP学习笔记 ZK-Learning MOOC课程笔记 Lecture 3: Programming ZKPs (Guest Lecturers: Pratyush Mishra and Alex Ozdemir) 3.3 Using a library ( tutorial) R1CS Libraries A library in a host language (Eg: Rust, OCaml, C, Go, …)Key type: constraint system Mai…

Jmeter —— 接口之间关联调用(获取上一个接口的返回值作为下一个接口的请求参数)

正则表达式&#xff1a; 具体如何操作&#xff1a; 1. 草稿保存&#xff0c; 此请求的响应数据的id 为发布总结的请求参数draft_id 2. 草稿保存的响应数据 3.在草稿保存的请求中&#xff0c;添加后置处理器- 正则表达式提取器&#xff0c; 提取响应数据的id信息 4. 发布总结请…

【vue2高德地图api】02-npm引入插件,在页面中展示效果

系列文章目录 提示&#xff1a;写完文章后&#xff0c;目录可以自动生成&#xff0c;如何生成可参考右边的帮助文档 文章目录 系列文章目录前言一、安装高德地图二、在main.js中配置需要配置2个key值以及1个密钥 三、在页面中使用3.1 新建路由3.2新建vue页面3.2-1 index.vue3.2…

深度学习——VGG与NiN网络

深度学习——VGG与NiN网络 文章目录 前言一、使用块的网络&#xff08;VGG&#xff09;1.1. VGG块1.2. VGG网络1.3. 模型训练1.4. 小结 二、网络中的网络&#xff08;NIN&#xff09;2.1. NIN块2.2. NIN模型2.3. 训练模型2.4. 小结 总结 前言 本章将学习使用块的网络&#xff…

c#设计模式-行为型模式 之 解释器模式

&#x1f680;简介 给定一个语言&#xff0c;定义它的文法表示&#xff0c;并定义一个解释器&#xff0c;这个解释器使用该标识来解释语言中的 句子。 解释器模式包含以下主要角色 抽象表达式&#xff08;Abstract Expression&#xff09;角色&#xff1a;定义解释器的接口&…

基于Java的考研信息查询系统设计与实现(源码+lw+部署文档+讲解等)

文章目录 前言具体实现截图论文参考详细视频演示为什么选择我自己的网站自己的小程序&#xff08;小蔡coding&#xff09; 代码参考数据库参考源码获取 前言 &#x1f497;博主介绍&#xff1a;✌全网粉丝10W,CSDN特邀作者、博客专家、CSDN新星计划导师、全栈领域优质创作者&am…

问题记录2 域名解析问题

上线部署时遇到内网域名解析问题&#xff1a; 内网域名为xxx.cn&#xff0c;在ip为yyy的服务器上&#xff0c;ping&#xff1a;xxx.cn 首先在服务器&#xff1a;yyy /etc/hosts查找缓存记录 cat /etc/hosts 127.0.0.1 VM-4-2-centos VM-4-2-centos 127.0.0.1 localhost.local…

【C+】C++11 —— 线程库

​ ​&#x1f4dd;个人主页&#xff1a;Sherry的成长之路 &#x1f3e0;学习社区&#xff1a;Sherry的成长之路&#xff08;个人社区&#xff09; &#x1f4d6;专栏链接&#xff1a;C学习 &#x1f3af;长路漫漫浩浩&#xff0c;万事皆有期待 上一篇博客&#xff1a;【C】C11…

基于SSM的视频播放系统的设计与实现

末尾获取源码 开发语言&#xff1a;Java Java开发工具&#xff1a;JDK1.8 后端框架&#xff1a;SSM 前端&#xff1a;Vue 数据库&#xff1a;MySQL5.7和Navicat管理工具结合 服务器&#xff1a;Tomcat8.5 开发软件&#xff1a;IDEA / Eclipse 是否Maven项目&#xff1a;是 目录…

网络库OKHttp(1)流程+拦截器

序、慢慢来才是最快的方法。 背景 OkHttp 是一套处理 HTTP 网络请求的依赖库&#xff0c;由 Square 公司设计研发并开源&#xff0c;目前可以在 Java 和 Kotlin 中使用。对于 Android App 来说&#xff0c;OkHttp 现在几乎已经占据了所有的网络请求操作。 OKHttp源码官网 版…

JVM垃圾回收算法介绍

堆的分代和区域 &#xff08;年轻代&#xff09;Young Generation&#xff08;eden、s0、s1 space&#xff09; Minor GC &#xff08;老年代&#xff09;Old Generation &#xff08;Tenured space&#xff09; Major GC|| Full GC &#xff08;永久代&#xff09;Permanent…

Qt之自定义插件(单控件,Qt设计师中使用)

文章目录 步骤1.选择项目类型2.设置项目名称3.选择合适的构建套件4.根据实际情况选择插件控件列表6.控件类生成&#xff08;默认勾选项&#xff09;7.构建生成项目及生成库位置&#xff08;默认&#xff09;8.库文件拷贝9.重启Qt查看效果 步骤 1.选择项目类型 如图选择‘其他…

oracle库中数据利用datax工具同步至mysql库

查看oracle版本 $sqlplus aaa/aaaa192.168.1.1/lcfaSQL*Plus: Release 19.0.0.0.0 - Production on Tue Oct 17 15:56:46 2023 Version 19.15.0.0.0Copyright (c) 1982, 2022, Oracle. All rights reserved.Last Successful login time: Tue Oct 17 2023 15:56:03 08:00Conne…

NSS [NISACTF 2022]easyssrf

NSS [NISACTF 2022]easyssrf 先看题目&#xff0c;给了一个输入框 看这提示就知道不是curl了&#xff0c;先file协议读一下flag&#xff0c;file:///flag 不能直接读flag&#xff0c;读个提示文件file:///fl4g 访问一下 <?phphighlight_file(__FILE__); error_reporting(0…

NSS [GWCTF 2019]枯燥的抽奖

NSS [GWCTF 2019]枯燥的抽奖 开题让我猜字符串&#xff0c;这种题目肯定不是猜&#xff0c;应该是类似于php伪随机数。 dirsearch扫他一下。 访问/check.php得到源码。 分析一下代码。 通过PHP伪随机数从字符库$str_long1中选取20个字符组成字符串&#xff0c;返回给我们前十…

EasyCVR视频汇聚平台显示有视频流但无法播放是什么原因?该如何解决?

视频汇聚/视频云存储/集中存储/视频监控管理平台EasyCVR能在复杂的网络环境中&#xff0c;将分散的各类视频资源进行统一汇聚、整合、集中管理&#xff0c;实现视频资源的鉴权管理、按需调阅、全网分发、云存储、智能分析等&#xff0c;视频智能分析平台EasyCVR融合性强、开放度…

BI零售数据分析,当代零售企业的核心竞争力

在数字化转型中&#xff0c;BI智能零售数据分析成为了极其重要的核心竞争力之一。通过对大数据的采集和分析&#xff0c;零售企业可以更好地了解消费者的需求和行为模式&#xff0c;从而做出更准确的决策。例如&#xff0c;通过分析消费者的购物历史、浏览记录等数据&#xff0…

五、WebGPU Vertex Buffers 顶点缓冲区

五、WebGPU Vertex Buffers 顶点缓冲区 在上一篇文章中&#xff0c;我们将顶点数据放入存储缓冲区中&#xff0c;并使用内置的vertex_index对其进行索引。虽然这种技术越来越受欢迎&#xff0c;但向顶点着色器提供顶点数据的传统方式是通过顶点缓冲和属性。 顶点缓冲区就像任…