今天我们来看一下单例模式,如何颠覆我们的认知。

先告诉大家单例模式有以下这些,我们来看看它是如何一步一步演化的吧!

  1. 饿汉式单例

  2. 懒汉式单例

  3. 注册式单例

  4. 本地线程单例

饿汉式单例


我们熟知的饿汉式单例是这样的

/** * @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++ 单例模式_你真的了解单例模式吗相关推荐

  1. python3 单例模式_当python,单例模式,多例模式,一次初始化遇到一起

    1.在python中,单例模式是很容易实现的,随便翻翻网上的相关教程,就能够找到很多答案. 比如这样: class hello(object): def __new__(cls, *args, **k ...

  2. print python 如何加锁_深度解密Python单例模式

    相关代码已经上传至Github:Python_Development_Interview,大家可以收藏专题-Python的设计模式:解密+实战,之后会持续更新相关的设计模式. 1. 认识单例模式 认识 ...

  3. 以下哪个选项不是单例模式的优点_深度解密Python单例模式

    相关代码已经上传至Github:Python_Development_Interview,大家可以收藏专题-Python的设计模式:解密+实战,之后会持续更新相关的设计模式. 1. 认识单例模式 认识 ...

  4. java单例模式 三种_三种java单例模式概述

    在java语言的应用程序中,一个类Class只有一个实例存在,这是由java单例模式实现的.Java单例模式是一种常用的软件设计模式,java单例模式分三种:懒汉式单例.饿汉式单例.登记式单例三种.下 ...

  5. java_设计模式_单例模式_

    目录 引言 一.概述 1.概念: 2.单例模式有 3 个特点: 3.单例模式的优点和缺点: 单例模式的优点: 单例模式的缺点: 单例模式的应用场景: 单例模式的结构: 二.单例模式的实现 1.饿汉式单 ...

  6. 什么是单例模式?为什么要用单例模式?实现的几种方式?

    Python 中的单例模式 单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实 ...

  7. 单例模式之饿汉式单例模式

    单例模式之饿汉式单例模式 单例模式三要素: 1.私有的静态属性,这主要是为了存储类唯一的实例 2.公共的静态方法,这主要是为了提供给外键生成获取单例的方法 3.用于限制类再次实例话的措施.一般会私有化 ...

  8. Java23种设计模式之单例模式的五种实现方式、反射破解单例模式、不能破解枚举单例模式详解

    源码链接(Gitee码云):https://gitee.com/oldou/javadesignpatterns 这里有我整理好的Java23种设计模式的源码以及博客教程,博客教程中介绍了Java23 ...

  9. python实现单例模式的几种方式_基于Python中单例模式的几种实现方式及优化详解...

    单例模式 单例模式(Singleton Pattern)是一种常用的软件设计模式,该模式的主要目的是确保某一个类只有一个实例存在.当你希望在整个系统中,某个类只能出现一个实例时,单例对象就能派上用场. ...

最新文章

  1. Python入门100题 | 第004题
  2. C++中的野指针问题
  3. 生产库自动派送报表派送失败之重新派送
  4. MySQL innodb_table_stats表不存在的解决方法
  5. Qt文档阅读笔记-Q_CLASSINFO官方解析与实例
  6. ML《集成学习(五)XGBoost》
  7. 私人笔记 -- 将图片插入到指定的单元格位置,并设置图片的宽度和高度
  8. wangEditor3菜单修改之如何添加分割线
  9. JavaScript模拟终端输出
  10. 开源小工具 酷狗、网易音乐缓存文件转mp3工具
  11. 形式语言与自动机 第三章 课后题答案
  12. python做大数据可视化软件_四款最受欢迎的大数据可视化工具
  13. doesn‘t work properly without JavaScript enabled. Please enable it to continue 的原因之一
  14. win7家庭普通版升级旗舰版 密钥
  15. 七夕节送女朋友什么礼物最好、七夕最走心的礼物清单
  16. 【8.6】代码源 - 【前缀集】【矩阵游戏】【谁才是最终赢家?】【放置多米诺骨牌】
  17. C语言 将整数n分解为若干质数(素数)之积
  18. 面试官:关于负载均衡你了解多少 | Nginx面试题 | Nginx架构
  19. codeforces 848E. Days of Floral Colours
  20. iOS KeyChain使用

热门文章

  1. Ibatis ISqlMapper工厂类案例
  2. VS2010测试方面的文章
  3. Scp远程批量执行命令
  4. NGINX:nginx精准禁止特定国家或者地区IP访问
  5. zabbix官方文档磁盘统计
  6. sar命令和vmstat命令详解
  7. WinMerge只显示差异部分的设置方法
  8. 遇到相同流程时,(在流程比较复杂时)「【充分利用】 在【 之前的 作业过程中 做成的(留下的) 资源】」 ~
  9. 【HDFS】HDFS与getconf结合使用,获取配置信息
  10. 【Excel-2010】空值替换