【设计模式】5种创建型模式详解

创建型模式提供创建对象的机制,能够提升已有代码的灵活性和复用性。

  • 常用的有:单例模式、工厂模式(工厂方法和抽象工厂)、建造者模式。
  • 不常用的有:原型模式。

一、单例模式

1.1 单例模式介绍

1 ) 定义

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一,此模式保证某个类在运行期间,只有一个实例对外提供服务,而这个类被称为单例类。

单例模式比较好理解,比如一个人一生当中只能有一个真实的身份证号,一个国家只有一个政府,类似的场景都是属于单例模式。

2 ) 使用单例模式要做的两件事

  1. 保证一个类只有一个实例
  2. 为该实例提供一个全局访问节点

3 ) 单例模式结构

1.2 饿汉式

在类加载期间初始化静态实例,保证 instance 实例的创建是线程安全的 ( 实例在类加载时实例化,有JVM保证线程安全).

特点: 不支持延迟加载实例(懒加载) , 此方式类加载比较慢,但是获取实例对象比较快。

问题: 该对象足够大的话,而一直没有使用就会造成内存的浪费。

public class Singleton_01 {//1. 私有构造方法private Singleton_01(){}//2. 在本类中创建私有静态的全局对象private static Singleton_01 instance = new Singleton_01();//3. 提供一个全局访问点,供外部获取单例对象public static  Singleton_01 getInstance(){return instance;}}

1.3 懒汉式(线程不安全)

此种方式的单例实现了懒加载,只有调用getInstance方法时才创建对象.但是如果是多线程情况,会出现线程安全问题.

public class Singleton_02 {//1. 私有构造方法private Singleton_02(){}//2. 在本类中创建私有静态的全局对象private static Singleton_02 instance;//3. 通过判断对象是否被初始化,来选择是否创建对象public static  Singleton_02 getInstance(){if(instance == null){instance = new Singleton_02();}return instance;}}

假设在单例类被实例化之前,有两个线程同时在获取单例对象,线程A在执行完if (instance == null) 后,线程调度机制将 CPU 资源分配给线程B,此时线程B在执行 if (instance == null)时也发现单例类还没有被实例化,这样就会导致单例类被实例化两次。为了防止这种情况发生,需要对 getInstance() 方法同步处理。改进后的懒汉模式.

1.4 懒汉式(线程安全)

原理: 使用同步锁 synchronized锁住 创建单例的方法 ,防止多个线程同时调用,从而避免造成单例被多次创建

实现思路:

  1. getInstance()方法块只能运行在1个线程中
  2. 若该段代码已在1个线程中运行,另外1个线程试图运行该块代码,则 会被阻塞而一直等待
  3. 而在这个线程安全的方法里我们实现了单例的创建,保证了多线程模式下 单例对象的唯一性
public class Singleton_03 {//1. 私有构造方法private Singleton_03(){}//2. 在本类中创建私有静态的全局对象private static Singleton_03 instance;//3. 通过添加synchronize,保证多线程模式下的单例对象的唯一性public static synchronized  Singleton_03 getInstance(){if(instance == null){instance = new Singleton_03();}return instance;}}

懒汉式的缺点也很明显,我们给 getInstance() 这个方法加了一把大锁(synchronzed),导致这个函数的并发度很低。量化一下的话,并发度是 1,也就相当于串行操作了。而这个函数是在单例使用期间,一直会被调用。如果这个单例类偶尔会被用到,那这种实现方式还可以接受。但是,如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈,这种实现方式就不可取了。

1.5 双重校验

饿汉式不支持延迟加载懒汉式有性能问题,不支持高并发。那我们再来看一种既支持延迟加载、又支持高并发的单例实现方式,也就是双重检测实现方式。

实现步骤:

  1. 在声明变量时使用了 volatile 关键字,其作用有两个:
  • 保证变量的可见性:当一个被volatile关键字修饰的变量被一个线程修改的时候,其他线程可以立刻得到修改之后的结果。
  • 屏蔽指令重排序:指令重排序是编译器和处理器为了高效对程序进行优化的手段,它只能保证程序执行的结果是正确的,但是无法保证程序的操作顺序与代码顺序一致。这在单线程中不会构成问题,但是在多线程中就会出现问题。
  1. 将同步方法改为同步代码块. 在同步代码块中使用二次检查,以保证其不被重复实例化 同时在调用getInstance()方法时不进行同步锁,效率高。

/*** 单例模式-双重校验* @author spikeCong* @date 2022/9/5**/
public class Singleton_04 {//使用 volatile保证变量的可见性private volatile static Singleton_04 instance = null;private Singleton_04(){}//对外提供静态方法获取对象public static Singleton_04 getInstance(){//第一次判断,如果instance不为null,不进入抢锁阶段,直接返回实例if(instance == null){synchronized (Singleton_04.class){//抢到锁之后再次进行判断是否为nullif(instance == null){instance = new Singleton_04();}}}return instance;}
}

在双重检查锁模式中为什么需要使用 volatile 关键字?

在java内存模型中,volatile 关键字作用可以是保证可见性或者禁止指令重排。这里是因为 singleton = new Singleton() ,它并非是一个原子操作,事实上,在 JVM 中上述语句至少做了以下这 3 件事:

  • 第一步是给 singleton 分配内存空间;
  • 第二步开始调用 Singleton 的构造函数等,来初始化 singleton;
  • 第三步,将 singleton 对象指向分配的内存空间(执行完这步 singleton 就不是 null 了)。

这里需要留意一下 1-2-3 的顺序,因为存在指令重排序的优化,也就是说第 2 步和第 3 步的顺序是不能保证的,最终的执行顺序,可能是 1-2-3,也有可能是 1-3-2。

如果是 1-3-2,那么在第 3 步执行完以后,singleton 就不是 null 了,可是这时第 2 步并没有执行,singleton 对象未完成初始化,它的属性的值可能不是我们所预期的值。假设此时线程 2 进入 getInstance 方法,由于 singleton 已经不是 null 了,所以会通过第一重检查并直接返回,但其实这时的 singleton 并没有完成初始化,所以使用这个实例的时候会报错.

详细流程如下图所示:

线程 1 首先执行新建实例的第一步,也就是分配单例对象的内存空间,由于线程 1 被重排序,所以执行了新建实例的第三步,也就是把 singleton 指向之前分配出来的内存地址,在这第三步执行之后,singleton 对象便不再是 null。

这时线程 2 进入 getInstance 方法,判断 singleton 对象不是 null,紧接着线程 2 就返回 singleton 对象并使用,由于没有初始化,所以报错了。最后,线程 1 “姗姗来迟”,才开始执行新建实例的第二步——初始化对象,可是这时的初始化已经晚了,因为前面已经报错了。

使用了 volatile 之后,相当于是表明了该字段的更新可能是在其他线程中发生的,因此应确保在读取另一个线程写入的值时,可以顺利执行接下来所需的操作。在 JDK 5 以及后续版本所使用的 JMM 中,在使用了 volatile 后,会一定程度禁止相关语句的重排序,从而避免了上述由于重排序所导致的读取到不完整对象的问题的发生。

1.6 静态内部类

  • 原理

根据 静态内部类 的特性(外部类的加载不影响内部类),同时解决了按需加载、线程安全的问题,同时实现简洁。

  1. 在静态内部类里创建单例,在装载该内部类时才会去创建单例
  2. 线程安全:类是由 JVM加载,而JVM只会加载1遍,保证只有1个单例

public class Singleton_05 {private static class SingletonHandler{private static Singleton_05 instance = new Singleton_05();}private Singleton_05(){}public static Singleton_05 getInstance(){return SingletonHandler.instance;}
}

1.7 反射对于单例的破坏

反射的概念: JAVA反射机制是在运行状态中,对于任意一个类,都能够知道这个类的所有属性和方法;对于任意一个对象,都能够调用它的任意方法和属性;这种动态获取信息以及动态调用对象方法的功能称为java语言的反射机制。

反射技术过于强大,它可以通过setAccessible()来修改构造器,字段,方法的可见性。单例模式的构造方法是私有的,如果将其可见性设为public,那么将无法控制对象的创建。

public class Test_Reflect {public static void main(String[] args) {try { //反射中,欲获取一个类或者调用某个类的方法,首先要获取到该类的Class 对象。Class<Singleton_05> clazz = Singleton_05.class;//getDeclaredXxx: 不受权限控制的获取类的成员.Constructor c = clazz.getDeclaredConstructor(null);//设置为true,就可以对类中的私有成员进行操作了c.setAccessible(true);Object instance1 = c.newInstance();Object instance2 = c.newInstance();System.out.println(instance1 == instance2);} catch (Exception e) {e.printStackTrace();}}}

解决方法之一: 在单例类的构造方法中 添加判断 instance != null 时,直接抛出异常

public class Singleton_05 {private static class SingletonHandler{private static Singleton_05 instance = new Singleton_05();}private Singleton_05(){if(SingletonHandler.instance != null){throw new RuntimeException("不允许非法访问!");}}public static Singleton_05 getInstance(){return SingletonHandler.instance;}
}

上面的这种方式使代码简洁性遭到破坏,设计不够优雅.

1.8 序列化对于单例的破坏

/*** 序列化对单例的破坏* @author spikeCong* @date 2022/9/6**/
public class Test_Serializable {@Testpublic void test() throws Exception{//序列化对象输出流ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("tempFile.obj"));oos.writeObject(Singleton.getInstance());//序列化对象输入流File file = new File("tempFile.obj");ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));Singleton Singleton = (Singleton) ois.readObject();System.out.println(Singleton);System.out.println(Singleton.getInstance());//判断是否是同一个对象System.out.println(Singleton.getInstance() == Singleton);//false}
}/*** 单例类实现序列化接口*/
class Singleton implements Serializable {private volatile static Singleton singleton;private Singleton() {}public static Singleton getInstance() {if (singleton == null) {synchronized (Singleton.class) {if (singleton == null) {singleton = new Singleton();}}}return singleton;}}

输出结构为false,说明:

通过对Singleton的序列化与反序列化得到的对象是一个新的对象,这就破坏了Singleton的单例性

解决方案

/**
* 解决方案:只要在Singleton类中定义readResolve就可以解决该问题
* 程序会判断是否有readResolve方法,如果存在就在执行该方法,如果不存在--就创建一个对象
*/
private Object readResolve() {return singleton;
}

问题是出在ObjectInputputStream 的readObject 方法上, 我们来看一下ObjectInputStream的readObject的调用栈:

ObjectInputStream中readObject方法的代码片段

try {Object obj = readObject0(false); //最终会返回一个object对象,其实就是序列化对象return obj;
} finally {passHandle = outerHandle;if (closed && depth == 0) {clear();}
}

ObjectInputStream中readObject0方法的代码片段

private Object readObject0(boolean unshared) throws IOException {case TC_OBJECT: //匹配如果是对象return checkResolve(readOrdinaryObject(unshared));
}

readOrdinaryObject方法的代码片段

private Object readOrdinaryObject(boolean unshared)throws IOException{//此处省略部分代码Object obj;try {//通过反射创建的这个obj对象,就是本方法要返回的对象,也可以暂时理解为是ObjectInputStream的readObject返回的对象。//isInstantiable:如果一个serializable的类可以在运行时被实例化,那么该方法就返回true//desc.newInstance:该方法通过反射的方式调用无参构造方法新建一个对象。obj = desc.isInstantiable() ? desc.newInstance() : null;} catch (Exception ex) {throw (IOException) new InvalidClassException(desc.forClass().getName(),"unable to create instance").initCause(ex);}return obj;}

到目前为止,也就可以解释,为什么序列化可以破坏单例了:?

答: 序列化会通过反射调用无参数的构造方法创建一个新的对象。

我们是如何解决的呢?

答: 只要在Singleton类中定义readResolve就可以解决该问题

//只要在Singleton类中定义readResolve就可以解决该问题
private Object readResolve() {return singleton;
}

实现原理

if (obj != null &&handles.lookupException(passHandle) == null &&desc.hasReadResolveMethod())
{Object rep = desc.invokeReadResolve(obj);if (unshared && rep.getClass().isArray()) {rep = cloneArray(rep);}if (rep != obj) {handles.setObject(passHandle, obj = rep);}
}

hasReadResolveMethod:如果实现了serializable 接口的类中包含readResolve则返回true

invokeReadResolve:通过反射的方式调用要被反序列化的类的readResolve方法。

总结: Singleton中定义readResolve方法,并在该方法中指定要返回的对象的生成策略,就可以防止单例被破坏。

1.9  枚举(推荐方式)

枚举单例方式是<<Effective Java>>作者推荐的使用方式,这种方式

在使用枚举时,构造方法会被自动调用,利用这一特性也可以实现单例;默认枚举实例的创建是线程安全的,即使反序列化也不会生成新的实例,任何情况下都是一个单例(暴力反射对枚举方式无效)。

特点: 满足单例模式所需的 创建单例、线程安全、实现简洁的需求

public enum Singleton_06{INSTANCE;private Object data;public Object getData() {return data;}public void setData(Object data) {this.data = data;}public static Singleton_06 getInstance(){return INSTANCE;}
}

问题1: 为什么枚举类可以阻止反射的破坏?

  1. 首先枚举类中是没有空参构造方法的,只有一个带两个参数的构造方法.
  2. 真正原因是: 反射方法中不予许使用反射创建枚举对象

异常: 不能使用反射方式创建enum对象

问题2: 为什么枚举类可以阻止序列化的破坏?

答:Java规范字规定,每个枚举类型及其定义的枚举变量在JVM中都是唯一的,因此在枚举类型的序列化和反序列化上,Ja

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

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

相关文章

自定义搭建管理系统

最近使用自己搭建的脚手架写了一个简易管理系统&#xff0c;使用webpackreactantd&#xff0c;搭建脚手架参考&#xff1a; 使用Webpack5搭建项目&#xff08;react篇&#xff09;_babel-preset-react-app-CSDN博客 搭建的思路&#xff1a; 1. 基建布局&#xff0c;使用antd的…

Spring ReflectionUtils 反射工具介绍和使用

一、ReflectionUtils 在 Java 中&#xff0c;反射&#xff08;Reflection&#xff09;是一种强大的机制&#xff0c;允许程序在运行时动态地检查类、获取类的信息、调用类的方法、访问或修改类的属性等。Java 的反射机制提供了一组类和接口&#xff0c;位于 java.lang.reflect…

光伏预测 | Matlab基于CNN-SE-Attention-ITCN的多特征变量光伏预测

光伏预测 | Matlab基于CNN-SE-Attention-ITCN的多特征变量光伏预测 目录 光伏预测 | Matlab基于CNN-SE-Attention-ITCN的多特征变量光伏预测预测效果基本描述模型简介程序设计参考资料 预测效果 基本描述 Matlab基于CNN-SE-Attention-ITCN的多特征变量光伏预测 运行环境: Matla…

轻量级模型,重量级性能,TinyLlama、LiteLlama小模型火起来了,针对特定领域较小的语言模型是否与较大的模型同样有效?

轻量级模型&#xff0c;重量级性能&#xff0c;TinyLlama、LiteLlama小模型火起来了&#xff0c;针对特定领域较小的语言模型是否与较大的模型同样有效? 当大家都在研究大模型&#xff08;LLM&#xff09;参数规模达到百亿甚至千亿级别的同时&#xff0c;小巧且兼具高性能的小…

多目标追踪概述

1. 目标跟踪分类 单目标跟踪&#xff1a;在视频的初始帧画面上框出单个目标&#xff0c;预测后续帧中该目标的大小与位置多目标跟踪&#xff1a;追踪多个目标的大小和位置&#xff0c;且每一帧中目标的数量和位置都可能变化 2. 多目标跟踪目前的主要问题 形态变化&#xff1a…

Android 获取USB相机支持的分辨率有多少

直接上代码 private fun getCamera() {// 获取系统相机服务val cameraManager requireContext().getSystemService(Context.CAMERA_SERVICE) as? CameraManagerif (cameraManager ! null) {// 在这里进行相机管理器的操作// 获取相机设备的 ID&#xff08;这里假设使用第一个相…

小封装高稳定性振荡器新系列(2.0 x 1.6 mm) 用于光学应用

小封装高稳定性振荡器新系列(2.0 x 1.6 mm) 用于光学应用&#xff0c;兼容OIF标准 Sg2016egn / sg2016vgn, sg2016ehn / sg2016vhn 来自光模块市场的需求爱普生提供SG2016系列解决方案SG2016系列:高稳定性&#xff0c;低抖动晶体振荡器规格尺寸&#xff0c;框图&#xff0c;引…

Java零基础 - 关键字 instanceof

哈喽&#xff0c;各位小伙伴们&#xff0c;你们好呀&#xff0c;我是喵手。 今天我要给大家分享一些自己日常学习到的一些知识点&#xff0c;并以文字的形式跟大家一起交流&#xff0c;互相学习&#xff0c;一个人虽可以走的更快&#xff0c;但一群人可以走的更远。 我是一名后…

无限创意之旅:深度挖掘Sora AI视频模型的可能性【文章底部添加可得内推码汇总表】

目录 引言 第一部分&#xff1a;Sora AI视频模型的特性 第二部分&#xff1a;Sora在创意领域的应用 第三部分&#xff1a;Sora对影视产业的影响 【文章底部添加可得内推码汇总表】 引言 21世纪&#xff0c;随着AI人工智能的迅猛发展&#xff0c;AI视频模型正成为数字创意领…

17.材质和外观

1.图形学中的材质 在图形学中&#xff0c;材质&#xff08;Material&#xff09;是用来描述物体外观和表面特性的属性集合。它包含了控制光的反射、折射、吸收以及其他光学效果的信息&#xff0c;从而决定了物体在渲染过程中的外观。 渲染方程中那一项和材质有关&#xff1f; …

HSE化工应急安全生产管理平台:信息化、流程化的安全管理新模式

随着化工行业的快速发展&#xff0c;安全生产管理日益成为企业发展的关键所在。在这一背景下&#xff0c;HSE化工应急安全生产管理平台应运而生&#xff0c;以信息平台为载体&#xff0c;数据驱动、风险管理为中心&#xff0c;致力于实现安全生产的动态、实时和智能化管理。本文…

【工具分享】批量查找文件并移动复制,咕嘎批量文件清单快速查找搜索文件,比bat批量查找文件并复制更简单一些

在工作中&#xff0c;像电商或者照相馆以及政府工程的工作人员&#xff0c;整理文件时&#xff0c;我们经常遇到批量查找部分文件&#xff0c;比如在10万个文件内查找5000个文件&#xff0c;把5000个文件分离出来&#xff0c;存在另外一个地方 如果是在电脑中挨个搜那要搜很久&…

客户至上!CRM系统如何助力企业提升客户满意度?

产品复购率是企业经营中的重要指标。要提升产品的复购率&#xff0c;除了产品质量需要过硬&#xff0c;客户服务的质量和效率也是重要影响因素&#xff0c;而CRM管理系统能够帮助达成这一点。 我们将通过这篇文章讲解CRM系统为何能提高客户满意度。 1.协助掌握客户的需要 企业…

【小尘送书-第十四期】《高效使用Redis:一书学透数据存储与高可用集群》

大家好&#xff0c;我是小尘&#xff0c;欢迎你的关注&#xff01;大家可以一起交流学习&#xff01;欢迎大家在CSDN后台私信我&#xff01;一起讨论学习&#xff0c;讨论如何找到满意的工作&#xff01; &#x1f468;‍&#x1f4bb;博主主页&#xff1a;小尘要自信 &#x1…

JAVA集合进阶(Set、Map集合)

一、Set系列集合 1.1 认识Set集合的特点 Set集合是属于Collection体系下的另一个分支&#xff0c;它的特点如下图所示 下面我们用代码简单演示一下&#xff0c;每一种Set集合的特点。 //Set<Integer> set new HashSet<>(); //无序、无索引、不重复 //Set<…

云HIS支持连锁集团化管理,1+N模式,支撑运营,管理,决策多位一体

目录 云HIS系统特色 使用简易化 连锁集团化 可扩展化 系统描述 云HIS系统优势 &#xff08;1&#xff09;客户/用户角度 &#xff08;2&#xff09;开发/运维角度 &#xff08;3&#xff09;成功应用案例 HIS分系统&#xff08;HIS子系统&#xff09; 1、医疗业务子…

el-submenu is-opened 展开/闭合;el-submenu is-opened保持一个子菜单的展开控制

写了个mes系统目录 点击子菜单展开后&#xff0c;上一级菜单没有默认关闭。主流后台管理系统大部分都是保持一个子菜单关闭状态、 问度娘无果后&#xff0c;查询官网&#xff0c;一个属性搞定。 unique-opened 是否只保持一个子菜单的展开 加在 <el-menu 组件上即可 完整代…

Excel工作表控件实现滚动按钮效果

实例需求&#xff1a;工作表中有多个Button控件&#xff08;工作表Form控件&#xff09;和一个ScrollBar控件&#xff08;工作表ActiveX控件&#xff0c;名称为ScrollBar2&#xff09;&#xff0c;需要实现如下图所示效果。点击ScrollBar控件实现按钮的滚动效果&#xff0c;实际…

SpringBoot自带的tomcat的最大连接数和最大的并发数

先说结果&#xff1a;springboot自带的tomcat的最大并发数是200&#xff0c; 最大连接数是&#xff1a;max-connectionsaccept-count的值 再说一下和连接数相关的几个配置&#xff1a; 以下都是默认值&#xff1a; server.tomcat.threads.min-spare10 server.tomcat.threa…

8.网络游戏逆向分析与漏洞攻防-游戏网络架构逆向分析-游戏底层功能对接类GameProc的实现

内容参考于&#xff1a;易道云信息技术研究院VIP课 上一个内容&#xff1a;通过逆向分析确定游戏明文接收数据过程 码云地址&#xff08;master 分支&#xff09;&#xff1a;https://gitee.com/dye_your_fingers/titan 码云版本号&#xff1a;bcf7559184863febdcad819e48aaa…