单例模式也能玩出花

一、单例模式

(1)单例模式

(2)单例模式实现要点

(3)使用场景
当频繁创建、销毁某个对象时,可以考虑单例模式。
当创建对象消耗资源过多时,但又经常使用时,可以考虑单例模式。

(1)实现方式

(2)饿汉式、懒汉式 区别

二、饿汉式

(1)基本说明

(2)代码实现(静态变量)
public 修饰变量,直接通过 “类名.变量名” 的方式获取对象。

(3)代码实现(静态方法)
private 修饰变量,不允许通过 “类名.变量名” 的形式访问。
public 修饰方法,通过 “类名.方法名” 的方式获取对象。

(4)代码实现(静态代码块)
静态代码块,只是将实例化操作 移动到 静态代码块中进行实现。

(5)这就完了吗?
当然不是了,这样写只是防止了通过 new 实例化对象。
对象实例化的方式还有 反射、序列化、克隆 等操作。
这些操作是否会破坏单例模式?需要思考一下。

(1)类主动使用时,才会进行类的初始化
类只有主动使用时,才会进行初始化操作。并不一定使用到类,就会触发初始化操作。
比如:
进行反射获取私有构造方法时,并不会触发 类加载过程。
如下代码执行后,静态代码块中的 “start…” 不会输出。

(2)反射破坏
如下代码所示,反射调用构造方法时,会进行类加载过程(输出 “start…” ),然后构建一个实例。
此时的实例对象是通过 构造方法重新创建的对象。与类加载过程中创建的对象不同。
即 反射对 单例模式造成了破坏。

(3)防止反射破坏(未必会生效)
在构造方法中,判断实例是否已经被创建。
类初始化过程中,会创建一个实例。即使通过反射调用构造方法,也会在实例创建之后再去调用,所以在 构造方法中进行判断,实例存在则会抛出异常。从而防止反射破坏(未必会生效,后续序列化破坏中有提到)。

(1)序列化、反序列化
序列化对象,并再次读取对象时(反序列化),会创建一个新的对象。
注:
序列化就是把实体对象状态按照一定的格式写入到有序字节流。
反序列化就是从有序字节流重建对象,恢复对象状态。

(2)反序列化破坏
如下代码所示,通过反序列化创建了个对象。
从实际代码执行结果看,反序列化仅触发了类加载过程(此时调用了构造函数),反序列化中 newInstance() 未主动触发类的构造函数,所以此处构造方法中的判断 无法防止 反序列化中反射的行为。
即 反序列化对 单例模式造成了破坏。

(3)防止反序列化破坏
通过 readResolve() 可以返回一个实例对象,保证此对象为类加载过程中创建的实例对象,即可防止 反序列化破坏。

(1)克隆破坏
如下代码所示,通过克隆创建了个对象。
调用了 Object 的 clone 方法(native 方法),与反序列化类似,也没有触发 类的构造方法(应该是直接从内存中 copy 了一份)。创建了一个新的对象。
即 克隆对 单例模式造成了破坏。

(2)防止克隆破坏
保证 clone() 方法返回的对象为类加载过程中创建的实例对象,即可防止 克隆破坏。

(1)基本说明

(2)反编译一下 enum 类

(3)防止反射破坏
枚举类型的类,没有无参构造。默认继承 Enum 的有参构造。

(4)防止克隆破坏
枚举类型的类,无法重写 clone() 方法。其父类 Enum 中定义 clone() 方法为 final 类型,不能被子类重写。

(5)防止序列化破坏
序列化返回的是同一个对象,无需定义 readResolve() 方法。其执行的是另一个逻辑。

三、懒汉式

(1)基本说明

(2)代码实现(静态方法)
如下代码所示,只允许通过 “类名.方法名” 的方式获取对象。

(3)这就完了吗?
当然不是了,这样写只是在单线程环境下正常执行。多线程操作下,会出现多个实例。
比如:
线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,执行 new 实例化操作,此时便会产生多个实例对象。
如下代码所示,代码执行多次可以发现,两个线程输出的对象并不一致。
此时单例模式被破坏,线程不安全。

(4)代码实现(synchronized 同步方法)
为了保证线程安全,可以使用 synchronized 关键字实现同步。
注:
synchronized 保证同一个时刻,只有一个线程可以执行某个方法或者某个代码块。
如下代码所示,在方法上添加一个 synchronized,代码执行多次可以发现,两个线程输出的对象始终一致。

(5)这就完了吗?
当然不是了,虽然使用 synchronized 保证线程安全,但是这种方式锁粒度太大,可能会导致执行效率低。

(6)代码实现(synchronized 同步代码块)
如下代码所示,为了缩小 synchronized 影响范围,可以在方法内部使用同步代码块的方式实现。

(7)这就完了吗?
当然不是了,这样写又回到了 静态方法中 提到的 线程不安全的问题上了。
比如:
线程 A 与线程 B 并发执行到 if (fullSingleton == null),此时两个线程的 fullSingleton 均为 null,则均会进入方法,遇到 synchronized,同步执行后,仍会执行 new 操作,产生多个实例对象。
此时单例模式被破坏,线程不安全。双重检查可以解决这个问题。

(1)代码实现
如下代码所示,双重检查,在 synchronized 同步代码块 的基础上,再添加一个判断。

(2)这就完了吗?
当然不是了。这样写看上去是保证了线程安全,但是有个细节需要思考一下(指令重排)。
如下所示,反编译一下代码,可以看到实例化操作的相关指令。

(3)代码实现(voliate )
使用 voliate 修饰 变量,禁止指令重排序。

(1)基本说明
静态内部类是一种结合了 饿汉模式、懒汉模式 优点的实现方式。

(2)代码实现
如下代码所示,定义一个静态内部类。

(3)这就完了吗?
当然不是了。反序列化破坏、反射破坏、克隆破坏 的问题同样存在。
解决方式与 饿汉模式的解决方式类似。

(4)防止序列化破坏
重写 readResolve() 方法,返回实例对象。

(5)防止克隆破坏
重写 clone() 方法,返回实例对象。

(6)防止反射破坏
在构造方法中,新增一个判断。

四、举例

(1)部分源码
如下代码所示,就是 饿汉模式的 实现。

Original: https://www.cnblogs.com/l-y-h/p/15578922.html
Author: 累成一条狗
Title: 单例模式也能玩出花

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/612584/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

  • 【XML】学习笔记第四章-schema

    Schema 概述 作用 与DTD相比Schema的优势 基础命名空间: 模式 引用方法 通过xsi:noNamespaceSchemaLocation引入 通过xsi:shema…

    Linux 2023年6月14日
    091
  • BootstrapTreeView 实现懒加载和点击事件。

    BootstrapTreeView的js下载位置:https://github.com/patternfly/patternfly-bootstrap-treeview。(注意不是…

    Linux 2023年6月7日
    0109
  • Redis 三种集群策略

    Redis 是单线程的,但是一般的作为缓存使用的话,速度已经足够使用。官方有一个简单测试:测试完成 50 个并发执行 100000 个请求,设置和获取的值是一个 256 字节字符串…

    Linux 2023年5月28日
    0103
  • Mac Mini 安装Ubuntu20.04 KVM

    在一台 Mac Mini mid 2011上安装Ubuntu20.04并配置KVM环境, 过程也适用于其他版本的Mac主机. I5 2415, 内存8G*2, 硬盘 SSD 500…

    Linux 2023年5月27日
    0107
  • 外键,查询关键字

    目录 自增特性 约束条件之外键 *查询关键字 内容 自增特性 自增不会随着数据的删除而回退 删除数据但无法重置主键 truncate 删除数据并重置主键值 约束条件之外键 简介 给…

    Linux 2023年6月7日
    0100
  • MySQL表空间回收的正确姿势

    不知道大家有没有遇到这样的一种情况,线上业务在MySQL表上做增删改查操作,随着时间的推移,表里面的数据越来越多,表数据文件越来越大,数据库占用的空间自然也逐渐增长 为了缩小磁盘上…

    Linux 2023年6月13日
    092
  • How to Operate SharePoint User Alerts with PowerShell

    When you migrate list or site, the user alerts in the site will not be migrated together w…

    Linux 2023年5月28日
    0143
  • Linux磁盘管理

    一、磁盘管理 Linux 磁盘管理好坏直接关系到整个系统的性能问题。 Linux 磁盘管理常用的三个命令为 df、 du 和 fdisk。 df(英文全称:disk full):列…

    Linux 2023年5月27日
    0100
  • 【已解决】linux centos7系统磁盘扩容

    第一步要手动加硬盘(我的操作是在20G的基础上加了30G) [reliable@hadoop102 ~]$ su root密码: 查看当前磁盘挂载情况: [root@hadoop1…

    Linux 2023年5月27日
    0106
  • Spring Boot连接数据库,从MySql5.13飞跃到MySql8.0.18的坑

    Spring Boot连接数据库,从MySql5.13飞跃到MySql8.0.18的坑 1.驱动名 驱动包用的是:mysql-connector-java-8.0.18.jar 驱…

    Linux 2023年6月7日
    089
  • 【Linux】指令学习

    Linux学习记录 😄生命不息,写作不止🏆 一个有梦有戏的人 @怒放吧德德🌝分享学习心得,欢迎指正,大家一起学习成长! 1、虚拟机网卡配置 服务器重启完成之后,我们可以通过linu…

    Linux 2023年6月6日
    0124
  • BKT的胡测题解:第一套第三题base

    博客园 :当前访问的博文已被密码保护 请输入阅读密码: Original: https://www.cnblogs.com/Grharris/p/11550809.htmlAuth…

    Linux 2023年6月6日
    0100
  • 机器学习入门笔记02–流行学习与图嵌入理论基础

    核化线性降维 线性降维方法假设从高维空间到低维空间的函数映射是线性的,然而,在不少现实任务中,可能需要非线性映射才能找到恰当的低维嵌入。 流行学习 “流形”…

    Linux 2023年6月6日
    085
  • Windows下PowerShell监控Keepalived

    一、 背景 某数据库服务器为CentOS,想要监控Keepalived的VIP是否有问题,通过邮件进行报警,但这台机器不能上外网,现在只能在Windows下通过PowerShell…

    Linux 2023年5月28日
    096
  • Redis中删除过期Key的三种策略

    转载自:http://blog.csdn.net/a_bang/article/details/52986935?locationNum=9&fps=1 项目中有个接口要频…

    Linux 2023年5月28日
    0111
  • 记一次PowerShell配合Metersploit的艰难提权

    0x01 环境准备 kali(模拟公网攻击机) Windows2008(靶机,装有360、火绒、安全狗、D盾) Powersploit(PowerShell攻击框架) https:…

    Linux 2023年5月28日
    085
亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球