【Java设计模式】二、单例模式

文章目录

  • 0、单例模式
  • 1、饿汉式
  • 2、懒汉式
  • 3、双重检查
  • 4、静态内部类
  • 5、枚举
  • 6、单例模式的破坏:序列化和反序列化
  • 7、单例模式的破坏:反射
  • 8、单例模式的实际应用

设计模式即总结出来的一些最佳实现。GoF(四人组) 书中提到23种设计模式,可分为三大类:

  • 创建型模式:隐藏了创建对象的过程,通过逻辑方法进行创建对象,使用者不用关注对象的创建细节(对那种属性很多,创建麻烦的对象尤其好用)
  • 结构型模式:主要关注类和对象的组合关系。将类或对象按某种布局组成更大的结构
  • 行为型模式:主要关注对象之间的通信与配合

在这里插入图片描述

0、单例模式

  • 单例模式即在程序中想要保持一个实例对象,让某个类只有一个实例
  • 单例类必须自己创建自己唯一的实例,并对外提供
  • 优点:减少了内存开销

单例模式的实现,有以下几种思路:

1、饿汉式

  • 类加载就会导致该单实例对象被创建
  • 通过静态代码块或者静态变量直接初始化

方式一:静态成员变量的方式

public class HungrySingleton {private static final HungrySingleton hungrySingleton = new HungrySingleton();//私有的构造方法,只能本类调用,不给外界用private HungrySingleton(){}//提供一个获取实例的方法给外界public static HungrySingleton getInstance(){return hungrySingleton;}
}

方式二:静态代码块

public class HungrySingleton {private static HungrySingleton hungrySingleton = null;//  静态代码块中进行赋值static {hungrySingleton = new HungrySingleton();}//私有的构造方法,只能本类调用,不给外界用private HungrySingleton(){}//提供一个获取实例的方法给外界public static HungrySingleton getInstance(){return hungrySingleton;}
}

以上两种方式,对象会随着类的加载而创建,如果这个对象后来一直没被用,就有点白占内存了。

2、懒汉式

类加载不会导致该单实例对象被创建,而是首次使用该对象时才会创建


懒汉式方式一:线程不安全
public class LazySingleton  {private static LazySingleton lazySingleton = null;/*** 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象*/private LazySingleton() {}/*** 单例对象的获取*/public static LazySingleton getInstance() {if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}}

测试类启两个线程获取对象:

public class Test {public static void main(String[] args) {new Thread(() -> {LazySingleton instance = LazySingleton.getInstance();System.out.println(Thread.currentThread().getName() + "-->" + instance);}, "t1").start();new Thread(() -> {LazySingleton instance = LazySingleton.getInstance();System.out.println(Thread.currentThread().getName()+ "-->" + instance);}, "t2").start();}
}

发现可能出现获取到两个不同对象的情况,这是因为线程安全问题:

在这里插入图片描述

两个线程A、B,同时执行IF 这一行,被挂起,再被唤醒时继续往下执行,就会创建出两个不同的实例对象。那最先想到的应该是synchronized关键字解决,但这样性能低下,因为不管对象是否为null,每次都要等着获取锁。

//性能低下,一刀切,不可行
public static synchronized LazySingleton getInstance() {if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}

3、双重检查

通过两个IF判断,加上同步锁进行实现。(懒汉式方式二:双重检查)

public class DoubleCheckSingleton {private static DoubleCheckSingleton doubleCheckSingleton;/*** 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象*/private DoubleCheckSingleton(){}public static DoubleCheckSingleton getInstance(){if(doubleCheckSingleton == null){synchronized (DoubleCheckSingleton.class){if(doubleCheckSingleton == null){doubleCheckSingleton = new DoubleCheckSingleton();}}}return doubleCheckSingleton;}
}

如此,再有A、B两个线程同时执行到第一个IF,只能有一个成功创建对象,另一个获取到锁后,第二重判断会告诉它已经有对象实例了。而亮点则在于,对象初始化完成后,后面来获取对象的线程不用等着拿锁,第一个IF就能告诉它已有对象,不用再等锁了。


以上双端检锁虽然线程安全,但问题是,JVM指令重排序后,可能出现空指针异常,可再加volatile关键字(volatile的可见性和有序性,这里用它的有序性)

//...
private static volatile DoubleCheckSingleton doubleCheckSingleton;
//...

4、静态内部类

在单例类中,通过私有的静态内部类,创建单例对象(加private修饰词的,出了本类无法调用和访问)。这也是懒汉式的第三种方式。

public class StaticInnerClassSingleton {/*** 私有的静态内部类实例化对象* 给内部类的属性赋值一个对象*/private static class InnerClass{private static StaticInnerClassSingleton staticInnerClassSingleton = new StaticInnerClassSingleton();}/*** 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象*/private StaticInnerClassSingleton(){}/*** 对外提供获取实例的方法*/public static StaticInnerClassSingleton getInstance(){return InnerClass.staticInnerClassSingleton;}
}

外部类StaticInnerClassSingleton被加载时,其对象不一定被初始化,因为内部类没有被主动使用到。直到调用getInstance方法时,静态内部类InnerClass被加载,完成实例化。

静态内部类在被加载时,不会立即实例化,而是在第一次使用时才会被加载并初始化。

JVM在加载外部类的过程中,不会加载静态内部类,只有内部类的属性或方法被调用时才会被加载,并初始化其静态属性。 这种延迟加载的特性,使得我们可以通过静态内部类来实现在需要时创建单例对象。这种方式没有线程安全问题,也没有性能影响和空间的浪费。

5、枚举

  • 单例模式的最佳实现方式
  • 枚举类型是线程安全的,并且只会装载一次
  • 可有效防止对单例模式的破坏
  • 属于饿汉式
public enum EnumSingleton {INSTANCE;public static EnumSingleton getInstance(){return INSTANCE;}
}

6、单例模式的破坏:序列化和反序列化

通过流将单例对象,序列化到文件中,然后再反序列化读取出来。发现通过反序列化方式创建出来的对象内存地址,和原对象不一样。或者对象序列化到文件后,两次反序列化得到的对象也不一样,单例模式被破坏。

public class TestSerializer {public static void main(String[] args) throws Exception {//懒汉式LazySingleton instance = LazySingleton.getInstance();//把对象序列化到文件中ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("singleton"));oos.writeObject(instance);//从文件中反序列化对象ObjectInputStream ois = new ObjectInputStream(new FileInputStream("singleton"));LazySingleton objInstance = (LazySingleton) ois.readObject();System.out.println(instance);System.out.println(objInstance);System.out.println(instance == objInstance);}
}

在这里插入图片描述

可以发现单例模式的五种实现方式中,只有枚举不会被破坏单例模式。如果非要用其他几种模式,可以加readResolve方法来重写反序列化逻辑。因为反序列化创建对象时,是通过反射创建的,反射会调用readResolve方法,并将其返回值做为反序列化的结果。 没有重写readResolve方法时,会通过反射创建一个新的对象,从而破坏了单例模式。这一点在对象流ObjectInputStream的源码可看出:

在这里插入图片描述

public class LazySingleton implements Serializable {private static LazySingleton lazySingleton = null;/*** 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象*/private LazySingleton() {}/*** 单例对象的获取*/public static LazySingleton getInstance() {if (lazySingleton == null) {lazySingleton = new LazySingleton();}return lazySingleton;}private Object readResolve(){return lazySingleton;}}

在这里插入图片描述

也可考虑使用@JsonCreator注解。

7、单例模式的破坏:反射

  • 通过字节码对象,创建构造器对象
  • 通过构造器对象,初始化单例对象
  • 由于单例对象的构造方法是private私有的,调用构造器中的方法,赋予权限,创建单例对象

注意私有修饰词时,反射会IllegalAccessException

在这里插入图片描述

处理下private问题,用懒汉模式验证:

public class TestReflect {public static void main(String[] args) throws Exception{//创建字节码对象Class<LazySingleton> clazz = LazySingleton.class;//构造器对象Constructor<LazySingleton> constructor = clazz.getDeclaredConstructor();//赋予权限constructor.setAccessible(true);//解决了私有化问题,获取实例对象LazySingleton o1 = constructor.newInstance();//再次反射LazySingleton o2 = constructor.newInstance();System.out.println(o1);System.out.println(o2);System.out.println(o1 == o2);}
}

在这里插入图片描述

用枚举的方式验证:

public class TestEnumReflect {public static void main(String[] args) throws Exception {Class<EnumSingleton> clazz = EnumSingleton.class;//枚举下的单例模式,创建构造方法时,需要给两个参数,薮泽NoSuchMethodException//这两个参数是源码中的体现,一个是String,一个是intConstructor<EnumSingleton> constructor = clazz.getDeclaredConstructor(String.class, int.class);constructor.setAccessible(true);EnumSingleton instanceReflect = constructor.newInstance("test",1234);EnumSingleton instanceSingleton = EnumSingleton.getInstance();System.out.println(instanceReflect);System.out.println(instanceSingleton);System.out.println(instanceReflect == instanceSingleton);}
}

运行报错:Cannot reflectively create enum objects,即反射创建枚举的单例对象,是不允许的:

在这里插入图片描述

在其他单例模式的实现方式里,也可以实现不允许通过反射创建对象,反射靠拿构造方法对象,调整下构造方法(比如双重检锁):

public class DoubleCheckSingleton {private static DoubleCheckSingleton doubleCheckSingleton;/*** 私有的构造方法,保证出了本类就不能再被调用,以防直接去创建对象*/private DoubleCheckSingleton(){if (doubleCheckSingleton != null) {throw new RuntimeException("不允许创建多个对象");}}public static DoubleCheckSingleton getInstance(){if(doubleCheckSingleton == null){synchronized (DoubleCheckSingleton.class){if(doubleCheckSingleton == null){doubleCheckSingleton = new DoubleCheckSingleton();}}}return doubleCheckSingleton;}
}

或者:

在这里插入图片描述

8、单例模式的实际应用

JDK的Runtime类就是饿汉式的单例模式:

在这里插入图片描述

PS:Runtime类的简单使用 --> 执行DOS命令并获取结果

public class RuntimeDemo {public static void main(String[] args) throws IOException {//获取Runtime类对象Runtime runtime = Runtime.getRuntime();//返回 Java 虚拟机中的内存总量。System.out.println(runtime.totalMemory());//返回 Java 虚拟机试图使用的最大内存量。System.out.println(runtime.maxMemory());//创建一个新的进程执行指定的字符串命令,返回进程对象Process process = runtime.exec("ipconfig");//获取命令执行后的结果,通过输入流获取InputStream inputStream = process.getInputStream();byte[] arr = new byte[1024 * 1024* 100];//将流输入到数组,返回读到的字节个数int b = inputStream.read(arr); //字节数组转字符串,指定下字符集为GBKSystem.out.println(new String(arr,0 ,b ,"gbk"));}
}

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

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

相关文章

B站画质补完计划(2):视频超分让像素细腻生动

本期作者 1 前言 为了给用户提供更清晰的画质体验&#xff0c;B站自研的超分辨率算法已经在站内广泛应用&#xff0c;支持了如《赛马娘》、《流浪地球2》、《权力的游戏》、英雄联盟S赛赛事直播等知名番剧、电影电视剧以及重要游戏赛事直播的 4K 视频流生产。 2 超分算法的应用…

论文阅读:2020GhostNet华为轻量化网络

创新&#xff1a;&#xff08;1&#xff09;对卷积进行改进&#xff08;2&#xff09;加残差连接 1、Ghost Module 1、利用1x1卷积获得输入特征的必要特征浓缩。利用1x1卷积对我们输入进来的特征图进行跨通道的特征提取&#xff0c;进行通道的压缩&#xff0c;获得一个特征浓…

“智农”-高标准农田

高标准农田是指通过土地整治、土壤改良、水利设施、农电配套、机械化作业等措施&#xff0c;提升农田质量和生产能力&#xff0c;达到田块平整、集中连片、设施完善、节水高效、宜机作业、土壤肥沃、生态友好、抗灾能力强、与现代农业生产和经营方式相适应的旱涝保收、稳产高产…

C++设计模式之——享元模式详解和代码案例

文章目录 C中实现享元模式通常涉及以下几个关键部分&#xff1a;一个简单的C代码片段示例享元模式的进一步说明C享元模式代码案例——咖啡店订单系统享元模式在现实世界的应用场景 C中实现享元模式通常涉及以下几个关键部分&#xff1a; 享元模式&#xff08;Flyweight Patter…

LCR 153. 二叉树中和为目标值的路径

解题思路&#xff1a; 回溯&#xff1a;先序遍历&#xff0b;路径记录 class Solution {LinkedList<List<Integer>> res new LinkedList<>();LinkedList<Integer> path new LinkedList<>();public List<List<Integer>> pathTarge…

android 如何动态修改swap

前言 当前项目中发现&#xff0c;产品在长时间使用后&#xff0c;会概率死机&#xff0c;通过log分析&#xff0c;可能和swap 大小太小导致的&#xff0c;需要修改增大swap大小后&#xff0c;压测验证。如何查看swap大小 cat /proc/swaps C:\Users\Administrator>adb shel…

元学习(meta-learning)的通俗解释

目录 1、什么是元学习 2、元学习还可以做什么 3、元学习是如何训练的 1、什么是元学习 meta-learning 的一个很经典的英文解释是 learn to learn&#xff0c;即学会学习。元学习是一个很宽泛的概念&#xff0c;可以有很多实现的方式&#xff0c;下面以目标检测的例子来解释…

阿里Replace Anything:一键替换万物,让图像编辑更简单

最近&#xff0c;阿里巴巴智能研究院在AIGC领域可谓动作频频&#xff0c;新品发布不断&#xff0c;在之前的文章已经向大家介绍了关于Animate AnyOne, Outfit Anyone&#xff0c;AnyText, AnyDoor等相关技术&#xff0c;感兴趣的小伙伴可以点击下面链接阅读&#xff5e; AI一键…

Laravel - API 项目适用的图片验证码

1. 安装 gregwar/captcha 图片验证码接口的流程是&#xff1a; 生成图片验证码 生成随机的 key&#xff0c;将验证码文本存入缓存。 返回随机的 key&#xff0c;以及验证码图片 # 不限于 laravel 普通 php 项目也可以使用额 $ composer require gregwar/captcha2. 开发接口 …

小塔RFID技术帮您解决“仓储管理危机”!

商品积压对一个企业带来的影响是久远的&#xff0c;仓储管理流转失衡&#xff1a;库存数据不准确、繁琐人工管理费时费力、商品爆仓及库存短缺等造成“仓储管理危机”&#xff0c;让企业自身陷入困境。 优化仓储管理&#xff0c;小塔RFID仓储管理方案轻松解决。利用RFID&#x…

java数据结构与算法刷题-----LeetCode538. 把二叉搜索树转换为累加树

java数据结构与算法刷题目录&#xff08;剑指Offer、LeetCode、ACM&#xff09;-----主目录-----持续更新(进不去说明我没写完)&#xff1a;https://blog.csdn.net/grd_java/article/details/123063846 文章目录 解题思路 BST二叉搜索树&#xff0c;中序遍历结果为一个升序序列…

【C语言】三子棋

前言&#xff1a; 三子棋是一种民间传统游戏&#xff0c;又叫九宫棋、圈圈叉叉棋、一条龙、井字棋等。游戏规则是双方对战&#xff0c;双方依次在9宫格棋盘上摆放棋子&#xff0c;率先将自己的三个棋子走成一条线就视为胜利。但因棋盘太小&#xff0c;三子棋在很多时候会出现和…

Unity(第十四部)光照

原始的有默认灯光、除了默认的你还可以创建 1、定向光源&#xff08;类似太阳、从无限远的地方射向地面的光&#xff0c;光源位置并不影响照射角度等&#xff0c;不同方向的旋转影响角度和明亮&#xff09; 1. 颜色&#xff1a;调整光的颜色2. 模式&#xff1a;混合是实时加烘…

FCU2601嵌入式控制单元获得开普「电磁兼容检验证书」

近日&#xff0c;飞凌嵌入式专为锂电池储能行业设计的FCU2601嵌入式控制单元获得了开普电磁兼容检验证书&#xff0c;此次性能检验项目包括高频干扰检验、静电放电干扰检验、辐射电磁场干扰检验、快速瞬变脉冲群干扰检验、浪涌干扰检验、工频磁场干扰检验、阻尼振荡磁场干扰检验…

基于docker实现MySQL主从复制(全网最详细!!!)

一、 通过docker镜像搭建MySQL主从 主服务器&#xff1a;容器名zi-mysql-master&#xff0c;端口3306 从服务器&#xff1a;容器名zi-mysql-slave1&#xff0c;端口3307 从服务器&#xff1a;容器名zi-mysql-slave2&#xff0c;端口3308 二、 关闭防火墙&#xff0c;启动docker…

免费百度快速收录软件

在网站SEO的过程中&#xff0c;不断更新网站内容是提升排名和吸引流量的关键之一。而对于大多数网站管理员来说&#xff0c;频繁手动更新文章并进行SEO优化可能会是一项繁琐且耗时的任务。针对这一问题&#xff0c;百度自动更新文章SEO工具应运而生&#xff0c;它能够帮助网站管…

CCF-A类 IEEE VIS‘24 3月31日截稿!探索可视化技术的无限可能!

会议之眼 快讯 IEEE VIS (IEEE Visualization Conference )即可视化大会将于 2024 年 10月13日 -18日在美国佛罗里达州皮特海滩的信风岛大海滩度假举行&#xff01;圣彼得海滩&#xff0c;以其迷人的日落和和煦的微风&#xff0c;作为激发创造力和促进可视化社区内合作的完美背…

工厂模式 详解 设计模式

工厂模式 其主要目的是封装对象的创建过程&#xff0c;使客户端代码和具体的对象实现解耦。这样子就不用每次都new对象&#xff0c;更换对象的话&#xff0c;所有new对象的地方也要修改&#xff0c;违背了开闭原则&#xff08;对扩展开放&#xff0c;对修改关闭&#xff09;。…

win中删除不掉的文件,火绒粉碎删除亲测有效

看网上的 win R 然后终端输入什么删除的&#xff0c;照做了都没有删掉 有火绒的可以试试&#xff1a; 拖进去就删掉了 很好使

选项 打光 试题总结

试题1 被测物体100100mm&#xff0c;精度要求被测物体 &#xff0c;精度要求0.1mm&#xff0c;相机距被测物体在200&#xff5e;320mm之间&#xff0c;要求选择合适的相机和镜头&#xff1f; 分析如下&#xff1a; 通常我们用的相机靶面是4:3 的所以我们要用短边来计算视场&am…