c++ 单例模式_你真的了解单例模式吗
先告诉大家单例模式有以下这些,我们来看看它是如何一步一步演化的吧!
饿汉式单例
懒汉式单例
注册式单例
本地线程单例
饿汉式单例
我们熟知的饿汉式单例是这样的
/** * @author ZerlindaLi create at 2020/9/4 11:20 * @version 1.0.0 * @description HungrySingleton * 饿汉模式 * 优点:代码简单 * 缺点:线程不安全,当单例很多时容易造成内存泄露 */public class HungrySingleton {private static final HungrySingleton hungrySingleton = new HungrySingleton(); private HungrySingleton(){}public HungrySingleton getInstance(){return hungrySingleton; }}
或者这样
public class HungryStaticSingleton {private static HungryStaticSingleton instance; static{instance = new HungryStaticSingleton(); }public HungryStaticSingleton getInstance(){return instance; }}
懒汉式单例
饿汉式单例在类加载时就会创建单例对象,当程序中有很多这样的单例时,又没有去调用,就会造成内存浪费。为了解决内存浪费这个问题,我们可以将实例延迟加载,当程序需要的时候,才实例化,就产生了下面的简单懒汉式单例
public class LazySimpleSingleton {private static LazySimpleSingleton lazySimpleSingleton; private LazySimpleSingleton(){}public static synchronized LazySimpleSingleton getInstance(){if(lazySimpleSingleton == null){lazySimpleSingleton = new LazySimpleSingleton(); }return lazySimpleSingleton; }}
为了避免线程安全问题,我们在方法上加了锁,但是这个会造成阻塞,只要请求这个方法,就要排队等候。那我们把锁加到方法里面只有实例对象为null时,才去创建,就是下面这个样子的
public class LazySimpleSingleton {private static LazySimpleSingleton lazySimpleSingleton; private LazySimpleSingleton(){}public static LazySimpleSingleton getInstance(){if(lazySimpleSingleton == null){synchronized (LazyStaticInnerClassSingleton.class){lazySimpleSingleton = new LazySimpleSingleton(); } }return lazySimpleSingleton; }}
我们仔细看看,发现这样依然不能解决线程安全问题。我们两个线程同时走到if判断,此时结果都为true, 那么他们都会执行锁以及所里面的问题。那我们想到,在执行锁里面的实例化过程之前,我们可以再加上一个判断,判断对象是否为空。就是下面这个代码,双重检查
public class LazyDoubleCheckSingleton {private volatile static LazyDoubleCheckSingleton instance; private LazyDoubleCheckSingleton(){}public static LazyDoubleCheckSingleton getInstance(){// 检查是否要阻塞 if(instance==null){synchronized (LazyDoubleCheckSingleton.class){// 检查是否要重新创建实例 if(instance == null){instance = new LazyDoubleCheckSingleton(); // 指令重排序的问题,所以使用了volatile } } }return instance; }}
我们看这个代码既解决了内存浪费和线程安全问题,有避免了阻塞。但是它不够优雅。我们有一种更优雅的写法,钻个java语法的空子,利用内部内的方式。来看一下代码
/** * @author ZerlindaLi create at 2020/9/4 16:08 * @version 1.0.0 * @description LazyStaticInnerClassSingleton * 优点:内部类使用时才会被jvm加载,初始化静态成员变量 * 缺点:会被反射破坏 */public class LazyStaticInnerClassSingleton {private LazyStaticInnerClassSingleton(){}public static LazyStaticInnerClassSingleton getInstance(){return LazyHolder.INSTANCE; }private static class LazyHolder{private static final LazyStaticInnerClassSingleton INSTANCE = new LazyStaticInnerClassSingleton(); }}
这个代码看似已经完美了,但是他会被发射破坏。我们来测试一下
/** * @author ZerlindaLi create at 2020/9/4 16:28 * @version 1.0.0 * @description ReferTest */public class ReflectTest {public static void main(String[] args) {try{ Class> clazz = LazyStaticInnerClassSingleton.class; Constructor c = clazz.getDeclaredConstructor(null); c.setAccessible(true); Object o1 = c.newInstance(); Object o2 = c.newInstance(); System.out.println(o1); System.out.println(o2); }catch (Exception e){ e.printStackTrace(); } }}
运行结果如图
可以看到,两次通过反射得到了两个不同的实例。那我们在构造器里面判断一下,如果已经存在实例对象,我们就抛出异常
private LazyStaticInnerClassSingleton(){if(LazyHolder.INSTANCE!=null){throw new RuntimeException("不允许非法访问"); }}
这样子通过反射来实例化的结果是什么呢?
我们进入newInstance()方法里面,可以看到样子一行代码
if ((clazz.getModifiers() & Modifier.ENUM) != 0)throw new IllegalArgumentException("Cannot reflectively create enum objects");
也就是说,枚举类型不会被反射破坏。
同时,以上所有单例在序列化和反序列化之后,都会得到两个不同的实例。我们来做个试验,用最简单的饿汉式单例
public class SeriableSingleton implements Serializable {private static final SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){}public static SeriableSingleton getInstance() {return INSTANCE; }}
做一个序列化的测试
/** * @author ZerlindaLi create at 2020/9/7 13:51 * @version 1.0.0 * @description SeriableSingletonTest */public class SeriableSingletonTest {public static void main(String[] args) { SeriableSingleton s1 = SeriableSingleton.getInstance(); SeriableSingleton s2 = null; try {// 序列化 FileOutputStream fos = new FileOutputStream("SeriableSingleton.txt"); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(s1); // 关闭流 oos.flush(); oos.close(); // 反序列化 FileInputStream fis = new FileInputStream("SeriableSingleton.txt"); ObjectInputStream ois = new ObjectInputStream(fis); s2 = (SeriableSingleton)ois.readObject(); ois.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } System.out.println(s1); System.out.println(s2); System.out.println(s1 == s2); }}
执行结果如下图
很明显得到了两个不同的对象。为了解决序列化的问题,我们走进readObject()方法来看一看。里面有一个readObject0(),找到类型为对象的
case TC_OBJECT:return checkResolve(readOrdinaryObject(unshared));
我们来看看readOrdinaryObject(unshared)里面做了什么
obj = desc.isInstantiable() ? desc.newInstance() : null;
判断了是否有无参构造器,有就创建一个实例
boolean isInstantiable() { requireInitialized(); return (cons != null);}
继续往下看,会看到这个
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) {// Filter the replacement object if (rep != null) {if (rep.getClass().isArray()) { filterCheck(rep.getClass(), Array.getLength(rep)); } else { filterCheck(rep.getClass(), -1); } }handles.setObject(passHandle, obj = rep); }}
这里判断了是否有readResolve()方法,如果有,就获取这个方法返回的对象,并且通过一些列处理之后赋值。那我们可以在类里面加上这个方法,并且将单例返回,如下
public class SeriableSingleton implements Serializable {private static final SeriableSingleton INSTANCE = new SeriableSingleton(); private SeriableSingleton(){}public static SeriableSingleton getInstance() {return INSTANCE; } private Object readResolve(){return INSTANCE; }}
注册式单例
前面我们说了枚举可以完美的避免反射破坏,那么是否也可以避免序列化破坏呢?下面我们可以通过枚举来完成单例模式
/** * @author ZerlindaLi create at 2020/9/4 17:39 * @version 1.0.0 * @description EnumSingleton * 枚举式单例: * 反射:在newInstance()方法里面,我们可以看到官方禁止了通过反射实例化枚举 * * 序列化:我们在readObject方法里面还是可以看到实际调用的是Enum的valueOf方法。而这个方法是通过map来取值的。 * public static > T valueOf(ClassenumType, * String name) { * T result = enumType.enumConstantDirectory().get(name); * if (result != null) * return result; * if (name == null) * throw new NullPointerException("Name is null"); * throw new IllegalArgumentException( * "No enum constant " + enumType.getCanonicalName() + "." + name); * } */public enum EnumSingleton{INSTANCE; private Object data; public Object getData() {return data; }public void setData(Object data) {this.data = data; }public static EnumSingleton getInstance(){return INSTANCE; }}
我们来测一下反射的结果
正如我们前面看到的,java官方不允许通过反射实例化枚举类型。
再来测试一下能否被序列化破坏
如上图所示,并没有。
我们针对枚举是怎么进行序列化的呢。依然进入readObject()方法,找到readObject0(),找到TC_ENUM,进入readEnum(unshared)
可以看到,它是通过Enum.valueOf((Class)cl, name)来得到一个实例的。我们进入valueOf来看看
注意到这里有一个enumConstantDirectory()方法,来看看
这个方法返回的是一个enumConstantDirectory对象
private volatile transient Map, T> enumConstantDirectory = null;
这是我们终于揭开它的神秘面纱了,它就是一个volatile和transient修饰的Map, 序列化时是通过类名和类对象类来找对唯一对应的枚举对象的。因此枚举对象不会被类加载器加载多次,所以它不会被序列化破坏。
但是我们细品,枚举类是不是在类初始化时就被jvm加载了,就实例化枚举对象了?那这又回到饿汉式单例的问题了,依旧会造成内存浪费,不适合创建大量单例的场景。那我们是否可以利用这个思路,用map自己来实现一个延迟加载的单例呢?
我们来看一下容器式单例模式
/** * @author ZerlindaLi create at 2020/9/4 17:39 * @version 1.0.0 * @description ContainerSingleton * Spring框架中单例的应用,ioc容器 */
public class ContainerSingleton {// 私有化构造器 private ContainerSingleton(){}private static Map, Object> ioc = new ConcurrentHashMap<>(); public static Object getBean(String className){if (!ioc.containsKey(className)) {synchronized (ioc){if(!ioc.containsKey(className)){ Object obj = null; try { obj = Class.forName(className).newInstance(); ioc.put(className, obj); } catch (ClassNotFoundException e) { e.printStackTrace(); } catch (IllegalAccessException e) { e.printStackTrace(); } catch (InstantiationException e) { e.printStackTrace(); }return obj; }else{return ioc.get(className); } } } else {return ioc.get(className); } }}
这里我们依然使用了双重锁机制来确保线程安全,又回到了前面的代码不优雅问题。所以到底哪种单例最好呢?我们要根据具体的场景,对比每种单例的优缺点,来选择合适的单例。
Spring容器其实也是一个单例模式,它也是将实例存放在一个map里面的,我们来看看Spring是怎么确保线程安全的
Threadlocal式单例
最后给大家一个彩蛋,本地线程式单例,天生的线程安全
/** * @author ZerlindaLi create at 2020/9/4 17:46 * @version 1.0.0 * @description ThreadlocalSingleton * 不能保证其创建的对象全局唯一,但能保证在单个线程中是唯一的,天生是线程安全的 */public class ThreadlocalSingleton {private static ThreadLocalthreadLocal = ThreadLocal.withInitial(() -> new ThreadlocalSingleton()); private ThreadlocalSingleton (){}public static ThreadlocalSingleton getInstance(){return threadLocal.get(); }}
大家可以尝试自己写一个测试类,来看看ThreadlocalSingleton是否是在单个线程里面线程安全。同时Threadlocal还有一个可以利用的点,就是能其他隔离做了。
看似简单的单例原来有这么多的门道了,看了这篇文章是否颠覆了你的认知呢?喜欢我请给我点一个再看,我会带来更多好内容!
c++ 单例模式_你真的了解单例模式吗相关推荐
- python3 单例模式_当python,单例模式,多例模式,一次初始化遇到一起
1.在python中,单例模式是很容易实现的,随便翻翻网上的相关教程,就能够找到很多答案. 比如这样: class hello(object): def __new__(cls, *args, **k ...
- print python 如何加锁_深度解密Python单例模式
相关代码已经上传至Github:Python_Development_Interview,大家可以收藏专题-Python的设计模式:解密+实战,之后会持续更新相关的设计模式. 1. 认识单例模式 认识 ...
- 以下哪个选项不是单例模式的优点_深度解密Python单例模式
相关代码已经上传至Github:Python_Development_Interview,大家可以收藏专题-Python的设计模式:解密+实战,之后会持续更新相关的设计模式. 1. 认识单例模式 认识 ...
- java单例模式 三种_三种java单例模式概述
在java语言的应用程序中,一个类Class只有一个实例存在,这是由java单例模式实现的.Java单例模式是一种常用的软件设计模式,java单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种.下 ...
- java_设计模式_单例模式_
目录 引言 一.概述 1.概念: 2.单例模式有 3 个特点: 3.单例模式的优点和缺点: 单例模式的优点: 单例模式的缺点: 单例模式的应用场景: 单例模式的结构: 二.单例模式的实现 1.饿汉式单 ...
- 什么是单例模式?为什么要用单例模式?实现的几种方式?
Python 中的单例模式 单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实 ...
- 单例模式之饿汉式单例模式
单例模式之饿汉式单例模式 单例模式三要素: 1.私有的静态属性,这主要是为了存储类唯一的实例 2.公共的静态方法,这主要是为了提供给外键生成获取单例的方法 3.用于限制类再次实例话的措施.一般会私有化 ...
- Java23种设计模式之单例模式的五种实现方式、反射破解单例模式、不能破解枚举单例模式详解
源码链接(Gitee码云):https://gitee.com/oldou/javadesignpatterns 这里有我整理好的Java23种设计模式的源码以及博客教程,博客教程中介绍了Java23 ...
- python实现单例模式的几种方式_基于Python中单例模式的几种实现方式及优化详解...
单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场. ...
最新文章
- Python入门100题 | 第004题
- C++中的野指针问题
- 生产库自动派送报表派送失败之重新派送
- MySQL innodb_table_stats表不存在的解决方法
- Qt文档阅读笔记-Q_CLASSINFO官方解析与实例
- ML《集成学习(五)XGBoost》
- 私人笔记 -- 将图片插入到指定的单元格位置,并设置图片的宽度和高度
- wangEditor3菜单修改之如何添加分割线
- JavaScript模拟终端输出
- 开源小工具 酷狗、网易音乐缓存文件转mp3工具
- 形式语言与自动机 第三章 课后题答案
- python做大数据可视化软件_四款最受欢迎的大数据可视化工具
- doesn‘t work properly without JavaScript enabled. Please enable it to continue 的原因之一
- win7家庭普通版升级旗舰版 密钥
- 七夕节送女朋友什么礼物最好、七夕最走心的礼物清单
- 【8.6】代码源 - 【前缀集】【矩阵游戏】【谁才是最终赢家?】【放置多米诺骨牌】
- C语言 将整数n分解为若干质数(素数)之积
- 面试官:关于负载均衡你了解多少 | Nginx面试题 | Nginx架构
- codeforces 848E. Days of Floral Colours
- iOS KeyChain使用