巨详细分析单例模式!禁止套娃?

2023-10-11 07:48

本文主要是介绍巨详细分析单例模式!禁止套娃?,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

单例模式

  • 饿汉模式-多线程安全
  • 懒汉模式-多线程不安全
  • 懒汉模式-多线程安全
  • 懒汉模式-双重校验锁(DCL,即 double-checked locking)-多线程安全
  • 静态内部类-多线程安全
  • 枚举-多线程安全且禁止反射
    • 强行使用反射破环枚举单例
  • 反射在双重校验锁创建下多个对象
  • 双重校验锁禁止反射
  • 双重校验锁不实例化对象直接反射
  • 使用标志防止双重校验锁下多次反射
  • 套娃永无止境

Java最简单的设计模式之一?
属于创建型模式,指一个类负责创建自己唯一的一个对象,对外提供一个的可以直接访问该对象的方式,而不需要实例化该类对象。

1.只能有一个实例
2.自己创建自己的唯一实例
3.必须对外提供该实例

安全是指普通情况下多线程安全,但除枚举方式外,均可通过反射获取多个对象

饿汉模式-多线程安全

public class HungryMan {private static HungryMan hungryMan=new HungryMan();private HungryMan(){}public static HungryMan getInstance(){return hungryMan;}
}

优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

懒汉模式-多线程不安全

public class LazyMan {private static LazyMan lazyMan;private LazyMan(){}public static LazyMan getInstance(){if (lazyMan==null) {lazyMan = new LazyMan();}return lazyMan;}
}
public static void main(String[] args) {System.out.println(LazyMan.getInstance());for (int i = 0; i < 10; i++) {new Thread(()->{System.out.println(LazyMan.getInstance());}).start();}}

运行如下,众所周知,当多个线程同时在lazyMan对象即将赋值时失去CPU调度权,之后继续运行,就会有多个不同的对象产生

在这里插入图片描述

懒汉模式-多线程安全

public class LazyMan {private static LazyMan lazyMan;private LazyMan(){}//加锁public static synchronized LazyMan getInstance(){if (lazyMan==null) {lazyMan = new LazyMan();}return lazyMan;}
}

优点:这种实现就是在方法上加了锁,保证了多线程安全,
缺点:频繁获取该单例对象时效率有所下降。

懒汉模式-双重校验锁(DCL,即 double-checked locking)-多线程安全

public class LazyMan {private static LazyMan lazyMan;private LazyMan() {}public static LazyMan getInstance() {//第一个if提高效率,多个线程只有在lazyMan为null时才会等待锁,如果已经创建过单例对象则直接返回if (lazyMan == null) {synchronized (LazyMan.class) {//确保单例安全,lazyMan为null时,只有第一个获得锁的线程才会创建该单例对象,其余线程会直接返回if (lazyMan == null) {lazyMan = new LazyMan();}}}return lazyMan;}
}

JDK1.5
是不是看起来没毛病?
然鹅, lazyMan = new LazyMan() 的操作并不是原子性的,需要经过:

  1. 为这个对象分配内存
  2. 执行构造方法,初始化对象
  3. 将对象指向内存空间

其中,步骤2和3可能会出现指令重排,
也就是执行了步骤1后,接着执行了步骤3,
假如其他线程此时到了第一个if (lazyMan == null)判断,会发现该对象的引用不为null,于是返回了该单例对象
但此时对象尚未完成初始化操作,
那么就拿到了一个不安全的单例对象

所以需要禁止指令重排,使用volatile修饰该对象即可保证线程安全

public class LazyMan {private volatile static LazyMan lazyMan;private LazyMan() {}public synchronized static LazyMan getInstance() {if (lazyMan == null) {synchronized (LazyMan.class) {if (lazyMan == null) {lazyMan = new LazyMan();}}}return lazyMan;}
}

静态内部类-多线程安全

public class Singleton {private Singleton(){}public static Singleton getInstance(){return InnerClass.instance;}public static class InnerClass{private static final Singleton instance = new Singleton();}
}

只有通过显式调用 getInstance 方法时,才会实例化 instance,可以实现懒加载

枚举-多线程安全且禁止反射

枚举在编译时会继承枚举类,并声明为final,天生防止反射,JDK1.5

public enum Singleton1{SINGLETON;public static Singleton1 getSingleton(){return SINGLETON;}
}
public class Test {public static void main(String[] args) {Singleton1 singleton1 = Singleton1.SINGLETON;//Singleton1 singleton1 = Singleton1.getSingleton();//等价于上Singleton1 singleton2 = Singleton1.SINGLETON;//Singleton1 singleton2 = Singleton1.getSingleton();//等价于上System.out.println("singleton1 = " + singleton1);System.out.println("singleton2 = " + singleton2);System.out.println(singleton1 == singleton2);//true}
}

在这里插入图片描述

ctrl+鼠标左键

在这里插入图片描述

若使用枚举的构造实例化对象则抛出异常

在这里插入图片描述

强行使用反射破环枚举单例

public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Singleton1 singleton1 = Singleton1.SINGLETON;Constructor<Singleton1> declaredConstructor = Singleton1.class.getDeclaredConstructor(String.class,int.class);declaredConstructor.setAccessible(true);Singleton1 singleton2 = declaredConstructor.newInstance();System.out.println(singleton1==singleton2);}
}

结果会失败并抛出异常

在这里插入图片描述

反射在双重校验锁创建下多个对象

public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {LazyMan instance = LazyMan.getInstance();//创建单例对象Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor();//获取LazyMan的无参构造器declaredConstructor.setAccessible(true);//设置构造器可操作LazyMan LazyMan1 = declaredConstructor.newInstance();//通过私有构造器实例化对象LazyMan LazyMan2 = declaredConstructor.newInstance();System.out.println("单例instance = " + instance);System.out.println("反射LazyMan1 = " + LazyMan1);System.out.println("反射LazyMan2 = " + LazyMan2);}

在这里插入图片描述

可以看到,三个对象完全不同,因为反射是通过操作私有构造器的方式创建出来的

双重校验锁禁止反射

public class LazyManBan {private volatile static LazyManBan lazyMan;private LazyManBan() {if (lazyMan != null) {throw new RuntimeException("请勿使用反射!");}}public static synchronized LazyManBan getInstance() {if (lazyMan == null) {synchronized (LazyManBan.class) {if (lazyMan == null) {lazyMan = new LazyManBan();}}}return lazyMan;}
}
public class Test {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {LazyManBan instance = LazyManBan.getInstance();//创建单例对象Constructor<LazyManBan> declaredConstructor = LazyManBan.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LazyManBan LazyManBan1 = declaredConstructor.newInstance();LazyManBan LazyManBan2 = declaredConstructor.newInstance();System.out.println("单例instance = " + instance);System.out.println("LazyManBan1 = " + LazyManBan1);System.out.println("LazyManBan2 = " + LazyManBan2);}
}

在这里插入图片描述

反射无法使用,你以为结束了吗?
反射无法使用是因为使用了getInstance()方法将单例对象lazyMan实例化
假如不赋值呢?也就是直接使用反射获取对象

双重校验锁不实例化对象直接反射

public class Test {public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {Constructor<LazyManBan> declaredConstructor = LazyManBan.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LazyManBan LazyManBan1 = declaredConstructor.newInstance();LazyManBan LazyManBan2 = declaredConstructor.newInstance();LazyManBan LazyManBan3 = declaredConstructor.newInstance();System.out.println("LazyManBan1 = " + LazyManBan1);System.out.println("LazyManBan2 = " + LazyManBan2);System.out.println("LazyManBan3 = " + LazyManBan3);}
}

在这里插入图片描述

对象还是被创建出来了
原因就是因为防止反射判断的是静态的lazyMan
是一个已经定义好了的对象
然而反射每次都是通过构造函数创建的对象,并没有将静态的lazyMan实例化,也就是两者没什么关系

使用标志防止双重校验锁下多次反射

public class LazyManBanFlag {private static boolean flag = false;private volatile static LazyManBanFlag lazyMan;private LazyManBanFlag() {if (!flag) {flag=true;}else {throw new RuntimeException("请勿使用反射!");}}public static synchronized LazyManBanFlag getInstance() {if (lazyMan == null) {synchronized (LazyManBanFlag.class) {if (lazyMan == null) {lazyMan = new LazyManBanFlag();}}}return lazyMan;}
}
public class Test {
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {LazyManBan instance = LazyManBan.getInstance();//创建单例对象Constructor<LazyManBanFlag> declaredConstructor = LazyManBanFlag.class.getDeclaredConstructor();declaredConstructor.setAccessible(true);LazyManBanFlag LazyManBanFlag1 = declaredConstructor.newInstance();LazyManBanFlag LazyManBanFlag2 = declaredConstructor.newInstance();System.out.println("单例instance = " + instance);System.out.println("LazyManBanFlag1 = " + LazyManBanFlag1);System.out.println("LazyManBanFlag2 = " + LazyManBanFlag2);}
}

在这里插入图片描述

此时无论是正常获取对象+反射
还是直接多次反射
都会抛出异常

套娃永无止境

你以为这就安全了吗?
反射也可以直接操作变量,private?
每次反射创建一个对象,设置标志为 false

这篇关于巨详细分析单例模式!禁止套娃?的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/186597

相关文章

Redis Cluster模式配置

《RedisCluster模式配置》:本文主要介绍RedisCluster模式配置,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友参考下吧... 目录分片 一、分片的本质与核心价值二、分片实现方案对比 ‌三、分片算法详解1. ‌范围分片(顺序分片)‌2. ‌哈希分片3. ‌虚

Android学习总结之Java和kotlin区别超详细分析

《Android学习总结之Java和kotlin区别超详细分析》Java和Kotlin都是用于Android开发的编程语言,它们各自具有独特的特点和优势,:本文主要介绍Android学习总结之Ja... 目录一、空安全机制真题 1:Kotlin 如何解决 Java 的 NullPointerExceptio

RabbitMQ工作模式中的RPC通信模式详解

《RabbitMQ工作模式中的RPC通信模式详解》在RabbitMQ中,RPC模式通过消息队列实现远程调用功能,这篇文章给大家介绍RabbitMQ工作模式之RPC通信模式,感兴趣的朋友一起看看吧... 目录RPC通信模式概述工作流程代码案例引入依赖常量类编写客户端代码编写服务端代码RPC通信模式概述在R

SQL Server身份验证模式步骤和示例代码

《SQLServer身份验证模式步骤和示例代码》SQLServer是一个广泛使用的关系数据库管理系统,通常使用两种身份验证模式:Windows身份验证和SQLServer身份验证,本文将详细介绍身份... 目录身份验证方式的概念更改身份验证方式的步骤方法一:使用SQL Server Management S

Redis高可用-主从复制、哨兵模式与集群模式详解

《Redis高可用-主从复制、哨兵模式与集群模式详解》:本文主要介绍Redis高可用-主从复制、哨兵模式与集群模式的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝... 目录Redis高可用-主从复制、哨兵模式与集群模式概要一、主从复制(Master-Slave Repli

一文带你搞懂Redis Stream的6种消息处理模式

《一文带你搞懂RedisStream的6种消息处理模式》Redis5.0版本引入的Stream数据类型,为Redis生态带来了强大而灵活的消息队列功能,本文将为大家详细介绍RedisStream的6... 目录1. 简单消费模式(Simple Consumption)基本概念核心命令实现示例使用场景优缺点2

Nginx location匹配模式与规则详解

《Nginxlocation匹配模式与规则详解》:本文主要介绍Nginxlocation匹配模式与规则,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、环境二、匹配模式1. 精准模式2. 前缀模式(不继续匹配正则)3. 前缀模式(继续匹配正则)4. 正则模式(大

Linux系统配置NAT网络模式的详细步骤(附图文)

《Linux系统配置NAT网络模式的详细步骤(附图文)》本文详细指导如何在VMware环境下配置NAT网络模式,包括设置主机和虚拟机的IP地址、网关,以及针对Linux和Windows系统的具体步骤,... 目录一、配置NAT网络模式二、设置虚拟机交换机网关2.1 打开虚拟机2.2 管理员授权2.3 设置子

SpringBoot如何通过Map实现策略模式

《SpringBoot如何通过Map实现策略模式》策略模式是一种行为设计模式,它允许在运行时选择算法的行为,在Spring框架中,我们可以利用@Resource注解和Map集合来优雅地实现策略模式,这... 目录前言底层机制解析Spring的集合类型自动装配@Resource注解的行为实现原理使用直接使用M

C#原型模式之如何通过克隆对象来优化创建过程

《C#原型模式之如何通过克隆对象来优化创建过程》原型模式是一种创建型设计模式,通过克隆现有对象来创建新对象,避免重复的创建成本和复杂的初始化过程,它适用于对象创建过程复杂、需要大量相似对象或避免重复初... 目录什么是原型模式?原型模式的工作原理C#中如何实现原型模式?1. 定义原型接口2. 实现原型接口3