[Java] 单例设计模式详解

模式定义:保证一个类只有一个实例,并且提供一个全局访问点,时一种创建型模式

使用场景:重量级的对象,不需要多个实例,如线程池,数据库连接池

单例设计模式的实现

1.懒汉模式:延迟加载,只有真正用到的时候才去做实例化

public class LazySingletonTest {
    public static void main(String[] args) {
        LazySingleton instance = LazySingleton.getInstance();
        LazySingleton instance1 = LazySingleton.getInstance();
        System.out.println(instance == instance1);
    }
}
class LazySingleton{

    private static LazySingleton instance;

    public static LazySingleton getInstance(){
        if (instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }
}

这是懒汉模式最基本的概念,就是什么时候需要了再什么时候创建

但是这样设计可能会有线程安全问题,同一时间两个访问就可能会new出来两个instance
最简单的解决思路就是给getInstance上锁

    public synchronized static LazySingleton getInstance(){
        if (instance == null){
            instance = new LazySingleton();
        }
        return instance;
    }

新出现的问题就是方法每次都加锁,完全没有必要,属于是性能浪费
那么可以进行一个锁的延迟

    public synchronized static LazySingleton getInstance(){
        if (instance == null){
        	synchronized (LazySingleton.class){
        		if(instance == null){	//还是防止第一次初始化两个线程竞争
        			instance = new LazySingleton();
        		}
        	}
        }
        return instance;
    }

从字节码来说,JIT或者CPU会再instance = new LazySingleton();这行代码的初始化和引用赋值进行重排序
在引用复制而没有初始化的中间又来了一个线程访问,发现instance已经赋值了,他就会直接拿到那个静态的instance而不是进入if,这就会导致他虽然拿到了,但是拿到的instance是空的,因为没有初始化
可以用volatile防止指令重排序来解决这个问题

	private volatile static LazySingleton instance;

2.饿汉模式:

类加载的初始化阶段就完成了类的初始化,本质上就是借助于jvm类的加载机制,保证实例的唯一性

public class hungrySingletonTest {
    public static void main(String[] args) {
        HungrySingleton instance = HungrySingleton.getInstance();
        HungrySingleton instance1 = HungrySingleton.getInstance();
        System.out.println(instance1 == instance);
    }
}

class HungrySingleton{
    private static HungrySingleton instance = new HungrySingleton();
    //不允许外部进行实例化
    private HungrySingleton(){}
    public static HungrySingleton getInstance(){
        return instance;
    }
}

饿汉模式赋值是在类加载的初始化阶段完成的,而类加载的是在真正的使用对应的类时,才会触发初始化

3. 静态内部类模式:

本质也是通过类加载的机制实现的懒加载的方式

class InnerClassSingleton{
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){}
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

与饿汉模式稍有差异,饿汉模式是在类加载的时候就直接把instance初始化了,而静态内部类的方式下,不调用getInstance()这个方法,是不会对里面的静态内部类InnerClassHolder进行加载,instance也不会初始化,所以也是一种懒加载
他的本质上也是利用类的加载机制来保证类的线程安全

防止反射攻击

这种单例的设计模式都是可能通过反射来获取新的实例对象的,如

public class InnerClassSingletonTest {
    public static void main(String[] args) throws Exception {
    	//通过反射获取他的构造器,然后通过构造器getInstance一个对象
        Constructor<InnerClassSingleton> declaredConstructor = InnerClassSingleton.class.getDeclaredConstructor();
        declaredConstructor.setAccessible(true);
        InnerClassSingleton innerClassSingleton = declaredConstructor.newInstance();

		//正常方法获取对象
        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        //返回flase
		System.out.println(instance == innerClassSingleton);
    }
}

class InnerClassSingleton{
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){}
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

下面给出防范措施,如饿汉模式和静态内部类的模式下,可以在构造器内进行判断是否有过初始化,如果有就说明这是通过反射等非正常手段获取的instance的,然后抛出异常

	class InnerClassSingleton{
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        if (InnerClassHolder.instance != null) {
            throw new RuntimeException("单例不允许多个实例!");
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

很明显,懒汉模式没有办法防止这种反射

枚举类型

首先我们看一下反射中constructor的newInstance()方法源码

    @CallerSensitive
    public T newInstance(Object ... initargs)
        throws InstantiationException, IllegalAccessException,
               IllegalArgumentException, InvocationTargetException
    {
        if (!override) {
            if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
                Class<?> caller = Reflection.getCallerClass();
                checkAccess(caller, clazz, null, modifiers);
            }
        }
        //-------!!!!!!!!!!!!!!!!!!!!---------
        //由这行代码可知,如果使用反射访问的类是个枚举类,那么就会抛出异常
        //由这个思路可以用枚举来防止反射攻击
        if ((clazz.getModifiers() & Modifier.ENUM) != 0)
            throw new IllegalArgumentException("Cannot reflectively create enum objects");
        ConstructorAccessor ca = constructorAccessor;   // read volatile
        if (ca == null) {
            ca = acquireConstructorAccessor();
        }
        @SuppressWarnings("unchecked")
        T inst = (T) ca.newInstance(initargs);
        return inst;
    }

下面是用枚举设计的最基础的单例模式

public class EnumSingletonTest {
    public static void main(String[] args) {
        EnumSingleton instance = EnumSingleton.INSTANCE;
        EnumSingleton instance1 = EnumSingleton.INSTANCE;
        System.out.println(instance1 == instance);
    }
}
enum EnumSingleton{
    INSTANCE;
    public void print(){
        System.out.println(this.hashCode());
    }
}

我们通过翻看字节码文件可以看到
字节码文件下EnumSingleton的构造器
enum类其实是继承了java.lang.Enum,
可以看到Enum的构造器
Enum类的构造器
所以要访问他的话,反射里面也要拿到这个构造器
最后根据构造器来newInstance出来
反射枚举类之后newInstance的结果图
可以看到,很好的防止反射进行攻击

序列化的单例

实际工作中,我们需要类可以序列化(如implements Serializable),之后进行数据传输
一个Instance在写入又读取的操作以后,读取不会走构造器,所以就会生成一个新的实例,破坏了单例的形式

//在静态内部类那个类做了个测试
public class InnerClassSingletonTest {
    public static void main(String[] args) throws Exception {

        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable"));
        oos.writeObject(instance);
        oos.close();

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
        InnerClassSingleton instance1 = (InnerClassSingleton)ois.readObject();
		ois.close();
        System.out.println(instance1 == instance); 	//输出false
    }
}

class InnerClassSingleton implements Serializable {
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        if (InnerClassHolder.instance != null) {
            throw new RuntimeException("单例不允许多个实例!");
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
}

来看看官方给出的解决方案
在这里插入图片描述
意思就是实现一个特殊的方法,然后返回你想要的那个单例就行了

public class InnerClassSingletonTest {
    public static void main(String[] args) throws Exception {

        InnerClassSingleton instance = InnerClassSingleton.getInstance();
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("testSerializable"));
        oos.writeObject(instance);

        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("testSerializable"));
        InnerClassSingleton instance1 = (InnerClassSingleton)ois.readObject();

        System.out.println(instance1 == instance);
    }
}

class InnerClassSingleton implements Serializable {
    static final long serialVersionUID = 42L;	//序列化版本号
    private static class InnerClassHolder{
        private static InnerClassSingleton instance = new InnerClassSingleton();
    }
    private InnerClassSingleton(){
        if (InnerClassHolder.instance != null) {
            throw new RuntimeException("单例不允许多个实例!");
        }
    }
    public static InnerClassSingleton getInstance(){
        return InnerClassHolder.instance;
    }
	//实现的readResolve方法
    Object readResolve() throws ObjectStreamException{
        return InnerClassHolder.instance;
    }
}

PS:切记要写个版本号,不然报错捏

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

066、故障处理之热点问题

为什么要解决热点 分布式架构中各个组件的理想状态&#xff1a;资源利用率相对均衡 形成写热点的原因 高频访问的小表SQL执行计划不合理具有顺序增长属性的索引扫描 数据组织模型 例如数据是序列递增&#xff0c;则有可能数据全部都集中一个region上 &#xff0c;或者集中…

6、用restful风格写controller方法接口,单元测试依赖

编写单元测试&#xff0c;用restful风格写controller方法 单元测试依赖 实际项目开发中&#xff0c;单元测试与业务代码通常都会要求同步进行 TDD测试驱动开发&#xff1a;先编写单元测试&#xff0c;然后努力去开发业务代码去满足所有的单元测试用例。 添加SpringBoot的测试…

Springer独立出版 | 2023年触觉与虚拟现实国际会议(ICHVR 2023)

会议简介 Brief Introduction 2023年触觉与虚拟现实国际会议(ICHVR 2023) 会议时间&#xff1a;2023年12月15日-17日 召开地点&#xff1a;中国北海 大会官网&#xff1a;www.ichvr.org 2023年触觉与虚拟现实国际会议(ICHVR 2023)由东南大学、上海交通大学联合主办&#xff1b;…

Python-ElasticSearch客户端的封装(聚合查询、统计查询、全量数据)

目录 ES Python客户端介绍封装代码测试代码参考 ES Python客户端介绍 官方提供了两个客户端elasticsearch、elasticsearch-dsl pip install elasticsearchpip install elasticsearch-dsl第二个是对第一个的封装&#xff0c;类似ORM操作数据库&#xff0c;可以.filter、.group…

com.android.ide.common.signing.KeytoolException:

签名没问题但是提示Execution failed for task :app:packageDebug. > A failure occurred while executing com.android.build.gradle.tasks.PackageAndroidArtifact$IncrementalSplitterRunnable > com.android.ide.common.signing.KeytoolException: Failed to read ke…

github gitlab 多用户多平台切换

一、背景 我需要用账号1 来登录并管理github 账号 我需要用账号2 来登录并管理gitlab 账号 二、设置账号 邮箱 设置账号1用户名与邮箱 git config --global user.name "miaojiang" git config --global user.email "187133163.com" 三、生成本地密钥…

pycharm配置arcpy环境

目录 1、安装ArcGIS软件2、安装PyCharm3、创建PyCharm项目4、验证ArcPy环境 在GIS开发中&#xff0c; ArcPy是不可或缺的重要组件&#xff0c;而PyCharm作为一款功能强大的Python IDE&#xff0c;为我们提供了更便捷、高效的开发环境。在本文中&#xff0c;我们将详细介绍如何…

sublime配置less的一些坑(1)

仅在sublime的Install Package安装保存less报错 在sublime的Install Package安装less 打开sublime软件,按住CtrlShiftP组合键,弹出的界面中选择Install Package 选中后enter或者回车。等会弹出一个弹窗,大致意思是说你已经成功安装了package control。如果你在此之前已经安装了…

【力扣每日一题】2023.7.31 重排链表

目录 题目&#xff1a; 示例: 分析: 代码: 题目&#xff1a; 示例: 分析: 给我们一个链表&#xff0c;让我们按照题目要求原地修改重排链表。 那么具体怎么个重排法呢&#xff0c;题目给出了一串式子&#xff0c;其实就是把链表分为前后两段&#xff0c;然后在前半段的节…

企业工程管理系统源码之提高工程项目管理软件的效率

高效的工程项目管理软件不仅能够提高效率还应可以帮你节省成本提升利润 在工程行业中&#xff0c;管理不畅以及不良的项目执行&#xff0c;往往会导致项目延期、成本上升、回款拖后&#xff0c;最终导致项目整体盈利下降。企企管理云业财一体化的项目管理系统&#xff0c;确保…

UML/SysML建模工具更新(2023.7)(1-5)有国产工具

DDD领域驱动设计批评文集 欢迎加入“软件方法建模师”群 《软件方法》各章合集 最近一段时间更新的工具有&#xff1a; 工具最新版本&#xff1a;Visual Paradigm 17.1 更新时间&#xff1a;2023年7月11日 工具简介 很用心的建模工具。支持编写用例规约。支持文本分析和C…

小目标检测(1)——大恒(DaHeng)相机操作与控制编程

文章目录 引言正文相关开发库的介绍编程准备配置引用头文件GalaxyIncludes.h配置lib文件 具体编程过程初始化和反初始化枚举设备开关设备 属性控制属性控制器种类 图像采集控制和图像处理采单帧回调采集图像处理流对象属性控制 获取设备事件获取掉线事件通知 样例程序分析补充&…

重生之我要学C++第五天

这篇文章主要内容是构造函数的初始化列表以及运算符重载在顺序表中的简单应用&#xff0c;运算符重载实现自定义类型的流插入流提取。希望对大家有所帮助&#xff0c;点赞收藏评论&#xff0c;支持一下吧&#xff01; 目录 构造函数进阶理解 1.内置类型成员在参数列表中的定义 …

2023.07.29 驱动开发DAY6

通过epoll实现一个并发服务器 服务器 #include <stdio.h> #include <string.h> #include <unistd.h> #include <sys/types.h> #include <sys/socket.h> #include <netinet/in.h> #include <arpa/inet.h> #include <sys/epoll.h…

脑电信号处理与特征提取——6.运用机器学习技术和脑电进行大脑解码(涂毅恒)

目录 六、运用机器学习技术和脑电进行大脑解码 6.1 前言 6.2 基于脑电数据的机器学习基础分析 6.3 基于脑电数据的机器学习进阶分析 6.4 代码解读 六、运用机器学习技术和脑电进行大脑解码 6.1 前言 6.2 基于脑电数据的机器学习基础分析 6.3 基于脑电数据的机器学习进阶分…

内网隧道代理技术(十五)之 Earthworm的使用(二级代理)

Earthworm的使用(二级代理) 本文紧接着上一篇文章继续讲解Earthworm工具的使用 (二级代理)正向连接 二级正向代理发生在如下的情况: 1、Web服务器在公网,黑客可以直接访问 2、B机器在内网,黑客不能直接访问 3、Web服务器可以访问内网机器B 4、内网机器B可以访问公司…

flask创建数据库连接池

flask创建数据库连接池 在Python中&#xff0c;您可以使用 Flask-SQLAlchemy 这个扩展来创建一个数据库连接池。Flask-SQLAlchemy 是一个用于 Flask 框架的 SQLAlchemy 操作封装&#xff0c;实现了 ORM(Object Relational Mapper)。ORM 主要用于将类与数据库中的表建立映射关系…

Spring Boot实践三 --数据库

一&#xff0c;使用JdbcTemplate访问MySQL数据库 1&#xff0c;确认本地已正确安装mysql 按【winr】快捷键打开运行&#xff1b;输入services.msc&#xff0c;点击【确定】&#xff1b;在打开的服务列表中查找mysql服务&#xff0c;如果没有mysql服务&#xff0c;说明本机没有…

【数据结构】实验十:哈夫曼编码

实验十 哈夫曼编码 一、实验目的与要求 1&#xff09;掌握树、森林与二叉树的转换&#xff1b; 2&#xff09;掌握哈夫曼树和哈夫曼编码算法的实现&#xff1b; 二、 实验内容 1. 请编程实现如图所示的树转化为二叉树。 2. 编程实现一个哈夫曼编码系统&#xff0c;系统功能…

腾讯云标准型S6/SA3/SR1/S5/SA2服务器CPU处理器大全

腾讯云服务器CVM标准型CPU处理器大全&#xff0c;包括标准型S6、SA3、SR1、S5、S5se、SA2、S4、SN3ne、S3、SA1、S2ne实例CPU处理器型号大全&#xff0c;标准型S6云服务器CPU采用Intel Ice Lake(2.7GHz/3.3GHz)&#xff0c;标准型S5采用Intel Xeon Cascade Lake 8255C/Intel Xe…
最新文章