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

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

相关文章

禁止平板,iPad长按弹出默认菜单事件

通过监控按下抬起时间差来禁止弹出事件,把以下代码写在要禁止的页面的页面加载事件里面即可     var date;document.addEventListener('touchstart', event => {date = new Date().getTime();});document.addEventListener('touchend', event => {if (new

在JS中的设计模式的单例模式、策略模式、代理模式、原型模式浅讲

1. 单例模式(Singleton Pattern) 确保一个类只有一个实例,并提供一个全局访问点。 示例代码: class Singleton {constructor() {if (Singleton.instance) {return Singleton.instance;}Singleton.instance = this;this.data = [];}addData(value)

模版方法模式template method

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/template-method 超类中定义了一个算法的框架, 允许子类在不修改结构的情况下重写算法的特定步骤。 上层接口有默认实现的方法和子类需要自己实现的方法

【iOS】MVC模式

MVC模式 MVC模式MVC模式demo MVC模式 MVC模式全称为model(模型)view(视图)controller(控制器),他分为三个不同的层分别负责不同的职责。 View:该层用于存放视图,该层中我们可以对页面及控件进行布局。Model:模型一般都拥有很好的可复用性,在该层中,我们可以统一管理一些数据。Controlller:该层充当一个CPU的功能,即该应用程序

迭代器模式iterator

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/iterator 不暴露集合底层表现形式 (列表、 栈和树等) 的情况下遍历集合中所有的元素

《x86汇编语言:从实模式到保护模式》视频来了

《x86汇编语言:从实模式到保护模式》视频来了 很多朋友留言,说我的专栏《x86汇编语言:从实模式到保护模式》写得很详细,还有的朋友希望我能写得更细,最好是覆盖全书的所有章节。 毕竟我不是作者,只有作者的解读才是最权威的。 当初我学习这本书的时候,只能靠自己摸索,网上搜不到什么好资源。 如果你正在学这本书或者汇编语言,那你有福气了。 本书作者李忠老师,以此书为蓝本,录制了全套视频。 试

禁止复制的网页怎么复制

禁止复制的网页怎么复制 文章目录 禁止复制的网页怎么复制前言准备工作操作步骤一、在浏览器菜单中找到“开发者工具”二、点击“检查元素(inspect element)”按钮三、在网页中选取需要的片段,锁定对应的元素四、复制被选中的元素五、粘贴到记事本,以`.html`为后缀命名六、打开`xxx.html`,优雅地复制 前言 在浏览网页的时候,有的网页内容无法复制。比如「360

利用命令模式构建高效的手游后端架构

在现代手游开发中,后端架构的设计对于支持高并发、快速迭代和复杂游戏逻辑至关重要。命令模式作为一种行为设计模式,可以有效地解耦请求的发起者与接收者,提升系统的可维护性和扩展性。本文将深入探讨如何利用命令模式构建一个强大且灵活的手游后端架构。 1. 命令模式的概念与优势 命令模式通过将请求封装为对象,使得请求的发起者和接收者之间的耦合度降低。这种模式的主要优势包括: 解耦请求发起者与处理者

springboot实战学习(1)(开发模式与环境)

目录 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 (3)前端 二、开发模式 一、实战学习的引言 (1)前后端的大致学习模块 (2)后端 Validation:做参数校验Mybatis:做数据库的操作Redis:做缓存Junit:单元测试项目部署:springboot项目部署相关的知识 (3)前端 Vite:Vue项目的脚手架Router:路由Pina:状态管理Eleme

状态模式state

学习笔记,原文链接 https://refactoringguru.cn/design-patterns/state 在一个对象的内部状态变化时改变其行为, 使其看上去就像改变了自身所属的类一样。 在状态模式中,player.getState()获取的是player的当前状态,通常是一个实现了状态接口的对象。 onPlay()是状态模式中定义的一个方法,不同状态下(例如“正在播放”、“暂停