[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();//返回flaseSystem.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()方法源码

    @CallerSensitivepublic 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 volatileif (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.mzph.cn/news/13510.shtml

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

相关文章

第一次后端复习整理(JVM、Redis、反射)

1. JVM 文章仅为自身笔记 详情查看一篇文章掌握整个JVM&#xff0c;JVM超详细解析&#xff01;&#xff01;&#xff01; 1.1 什么是JVM jvm是Java虚拟机 1.2 Java文件的编译过程 程序员编写代码形成.java文件经过javac编译成.class文件再通过JVM的类加载器进入运行时数据…

【141. 环形链表】

来源&#xff1a;力扣&#xff08;LeetCode&#xff09; 描述&#xff1a; 给你一个链表的头节点 head &#xff0c;判断链表中是否有环。 如果链表中有某个节点&#xff0c;可以通过连续跟踪 next 指针再次到达&#xff0c;则链表中存在环。 为了表示给定链表中的环&#x…

JVM | 基于类加载的一次完全实践

引言 我在上篇文章&#xff1a;JVM | 类加载是怎么工作的 中为你介绍了Java的类加载器及其工作原理。我们简单回顾下&#xff1a;我用一个易于理解的类比带你逐步理解了类加载的流程和主要角色&#xff1a;引导类加载器&#xff0c;扩展类加载器和应用类加载器。并带你深入了解…

剑指offer12 矩阵中的路径 13 机器人的运动范围 34.二叉树中和为某一值得路径

class Solution { public:bool exist(vector<vector<char>>& board, string word) {int rowboard.size(),colboard[0].size();int index0,i0,j0;if(word.size()>row*col) return 0;//vector<vector<int>> visit[row][col];//标记当前位置有没有…

算法之归并排序算法

归并&#xff08;Merge&#xff09;排序法是将两个&#xff08;或两个以上&#xff09;有序表合并成一个新的有序表&#xff0c;即把待排序序列 分为若干个子序列&#xff0c;每个子序列是有序的。然后再把有序子序列合并为整体有序序列。 public class MergeSortTest {public …

到底什么是前后端分离

目录 Web 应用的开发主要有两种模式&#xff1a; 前后端不分离 前后端分离 总结 Web 应用的开发主要有两种模式&#xff1a; 前后端不分离 前后端分离 理解它们的区别有助于我们进行对应产品的测试工作。 前后端不分离 在早期&#xff0c;Web 应用开发主要采用前后端不…

linux系统下(centos7.9)安装Jenkins全流程

一、卸载历史版本 # rpm卸载 rpm -e jenkins# 检查是否卸载成功 rpm -ql jenkins# 彻底删除残留文件 find / -iname jenkins | xargs -n 1000 rm -rf二、环境依赖安装 yum -y install epel-releaseyum -y install daemonize三、安装Jenkins Jenkins官网传送带&#xff1a; …

《零基础入门学习Python》第071讲:GUI的终极选择:Tkinter8

虽然我们能用 tkinter 设计不少东西了&#xff0c;但是不少同学还是感觉对这个界面编程掌控得还不够多&#xff0c;说白了&#xff0c;就是我们现在还没办法随心所欲的去绘制我们想要的界面&#xff0c;但是不瞒你说&#xff0c;今天的这一节课将会给你的人生乃至人生观带来翻天…

苍穹外卖-day07

苍穹外卖-day07 本项目学自黑马程序员的《苍穹外卖》项目&#xff0c;是瑞吉外卖的Plus版本 功能更多&#xff0c;更加丰富。 结合资料&#xff0c;和自己对学习过程中的一些看法和问题解决情况上传课件笔记 视频&#xff1a;https://www.bilibili.com/video/BV1TP411v7v6/?sp…

Rust vs Go:常用语法对比(五)

题图来自 Rust vs Go 2023[1] 81. Round floating point number to integer Declare integer y and initialize it with the rounded value of floating point number x . Ties (when the fractional part of x is exactly .5) must be rounded up (to positive infinity). 按规…

KWP2000协议和OBD-K线

KWP2000最初是基于K线的诊断协议&#xff0c; 但是由于后来无法满足越来越复杂的需求&#xff0c;以及自身的局限性&#xff0c;厂商又将这套应用层协议移植到CAN上面&#xff0c;所以有KWP2000-K和KWP2000-CAN两个版本。 这篇文章主要讲基于K线的早期版本协议&#xff0c;认…

零售企业信息化系统建设与应用解决方案

导读&#xff1a;原文《零售企业信息化系统建设与应用解决方案ppt》&#xff08;获取来源见文尾&#xff09;&#xff0c;本文精选其中精华及架构部分&#xff0c;逻辑清晰、内容完整&#xff0c;为快速形成售前方案提供参考。 完整版领取方式 如需获取完整的电子版内容参考学习…

基于Kaggle训练集预测的多层人工神经网络的能源消耗的时间序列预测(Matlab代码实现)

目录 &#x1f4a5;1 概述 &#x1f4da;2 运行结果 &#x1f308;3 Matlab代码实现 &#x1f389;4 参考文献 &#x1f4a5;1 概述 本文为能源消耗的时间序列预测&#xff0c;在Matlab中实现。该预测采用多层人工神经网络&#xff0c;基于Kaggle训练集预测未来能源消耗。 &am…

使用镜像搭建nacos集群

安装并配置 docker 1 先安装docker //1.查看操作系统的发行版号 uname -r//2.安装依赖软件包 yum install -y yum-utils device-mapper-persistent-data lvm2//3.设置yum镜像源 //官方源&#xff08;慢&#xff09; yum-config-manager --add-repo http://download.docker.co…

第十二章:priority_queue类

系列文章目录 文章目录 系列文章目录前言priority_queue的介绍priority_queue的使用容器适配器什么是容器适配器STL标准库中stack和queue的底层结构 总结 前言 priority_queue是容器适配器&#xff0c;底层封装了STL容器。 priority_queue的介绍 priority_queue文档介绍 优先…

IDEA中文UT方法执行报错问题、wps默认保存格式

wps默认保存格式、IDEA中文UT方法执行报错问题 背景 1、wps修改文件后&#xff0c;编码格式从UTF-8-bom变成UTF-8&#xff08;notepad可以查看&#xff09;&#xff1b; 2、IDEA中文UT执行报错&#xff1a; 解决方案 1、语言设置中不要勾选 “Beta版。。。。” 2、cmd中执…

layui框架学习(33:流加载模块)

Layui中的流加载模块flow主要支持信息流加载和图片懒加载两部分内容&#xff0c;前者是指动态加载后续内容&#xff0c;示例的话可以参考csdn个人博客主页&#xff0c;鼠标移动到页面底部时自动加载更多内容&#xff0c;而后者是指页面显示图片时才会延迟加载图片信息。   fl…

RocketMQ 行业分享

5.0的架构发生了重大调整&#xff0c;添加了一层rocketmq-proxy,可以通过grpc的方式接入。 参考 https://juejin.cn/post/7199413150973984827

UE5 关于MRQ渲染器参数

最佳参数&#xff1a; Spatial Sample Count&#xff1a;使用奇数样本时临时抗锯齿会收敛 Temporal Sample Count&#xff1a;超过2之后&#xff0c;采样过大会造成TAA效果不佳 TSR&#xff1a;UE5最好的抗锯齿方案

AI聊天GPT三步上篮!

1、是什么&#xff1f; CHATGPT是OpenAI开发的基于GPT&#xff08;Generative Pre-trained Transformer&#xff09;架构的聊天型人工智能模型。也就是你问它答&#xff0c;根据网络抓去训练 2、怎么用&#xff1f; 清晰表达自己诉求&#xff0c;因为它就是一个AI助手&#…