0


【设计模式】单例模式


文章目录

单例模式详解

0.概述

为什么要使用单例模式?

在我们的系统中,有一些对象其实我们只需要一个,比如说:线程池、缓存、对话框、注册表、日志对象、充当打印机、显卡等设备驱动程序的对象。事实上,这一类对象只能有一个实例,如果制造出多个实例就可能会导致一些问题的产生,比如:程序的行为异常、资源使用过量、或者不一致性的结果。因此这里需要用到单例模式

使用单例模式的好处?

  • 对于频繁使用的对象,可以省略创建对象所花费的时间,这对于那些重量级对象而言,是非常可观的一笔系统开销
  • 由于 new 操作的次数减少,因而对系统内存的使用频率也会降低,这将减轻 GC 压力,缩短 GC 停顿时间

1.饿汉式

1.1 饿汉式单例实现

实例会提前创建:

/**
* 饿汉式
*
* @author xppll
* @date 2021/12/24 21:21
*/publicclassSingleton1implementsSerializable{//构造私有privateSingleton1(){System.out.println("private Singleton1()");}//唯一实例privatestaticfinalSingleton1 INSTANCE =newSingleton1();//获得实例方法publicstaticSingleton1getINSTANCE(){return INSTANCE;}//其他方法publicstaticvoidotherMethod(){System.out.println("otherMethod()");}}

测试:

/**
 * @author xppll
 * @date 2021/12/24 21:28
 */publicclassTestSingleton{publicstaticvoidmain(String[] args){//触发Singleton1类的初始化,会为类的静态变量赋予正确的初始值,单例对象就会被创建!Singleton1.otherMethod();System.out.println("-----------------------------------");System.out.println(Singleton1.getINSTANCE());System.out.println(Singleton1.getINSTANCE());}}//输出:privateSingleton1()otherMethod()[email protected][email protected]

1.2 破坏单例的几种情况

  1. 反射破坏单例
  2. 反序列化破坏单例
  3. Unsafe破坏单例

演示:

/**
 * @author xppll
 * @date 2021/12/24 21:28
 */publicclassTestSingleton{publicstaticvoidmain(String[] args)throwsInvocationTargetException,NoSuchMethodException,InstantiationException,IllegalAccessException,IOException,ClassNotFoundException{//触发Singleton1类的初始化,会为类的静态变量赋予正确的初始值,单例对象就会被创建!Singleton1.otherMethod();System.out.println("-----------------------------------");System.out.println(Singleton1.getINSTANCE());System.out.println(Singleton1.getINSTANCE());//反射破坏单例reflection(Singleton1.class);//反序列化破坏单例serializable(Singleton1.getINSTANCE());//Unsafe破坏单例unsafe(Singleton1.class);}//反射破坏单例privatestaticvoidreflection(Class<?> clazz)throwsNoSuchMethodException,InvocationTargetException,InstantiationException,IllegalAccessException{//得到无参Constructor<?> constructor = clazz.getDeclaredConstructor();//将此对象的 accessible 标志设置为指示的布尔值,即设置其可访问性
        constructor.setAccessible(true);//创建实例System.out.println("反射创建实例:"+ constructor.newInstance());}//反序列化破坏单例privatestaticvoidserializable(Object instance)throwsIOException,ClassNotFoundException{ByteArrayOutputStream bos =newByteArrayOutputStream();ObjectOutputStream oos =newObjectOutputStream(bos);//序列化
        oos.writeObject(instance);ObjectInputStream ois =newObjectInputStream(newByteArrayInputStream(bos.toByteArray()));//反序列化System.out.println("反序列化创建示例:"+ ois.readObject());}//Unsafe破坏单例privatestaticvoidunsafe(Class<?> clazz)throwsInstantiationException{Object o =UnsafeUtils.getUnsafe().allocateInstance(clazz);System.out.println("Unsafe 创建实例:"+ o);}}

结果:
在这里插入图片描述

可以看出三种方式都会破坏单例!

1.3 预防单例的破坏

预防反射破坏单例

在构造方法中加个判断即可:

//构造私有privateSingleton1(){//防止反射破坏单例if(INSTANCE!=null){thrownewRuntimeException("单例对象不能重复创建");}System.out.println("private Singleton1()");}

预防反序列化破坏单例

Singleton1()

中重写

readResolve

方法:

//重写这个方法,如果序列化了,就会返回这个,不会返回反序列化的对象publicObjectreadResolve(){return  INSTANCE;}

Unsafe破坏单例无法预防

2.枚举饿汉式

2.1 枚举单例实现

枚举实现单例:

/**
 * 枚举实现单例
 *
 * @author xppll
 * @date 2021/12/24 22:23
 */publicenumSingleton2{
    INSTANCE;//枚举的构造方法默认是private的,可以不写Singleton2(){System.out.println("private Singleton2()");}//重写toString方法@OverridepublicStringtoString(){returngetClass().getName()+"@"+Integer.toHexString(hashCode());}//获得实例方法(这个可以不要,枚举变量都是public的)publicstaticSingleton2getInstance(){return INSTANCE;}//其他方法publicstaticvoidotherMethod(){System.out.println("otherMethod()");}}

测试:

/**
 * @author xppll
 * @date 2021/12/24 21:28
 */publicclassTestSingleton{publicstaticvoidmain(String[] args)throwsInvocationTargetException,NoSuchMethodException,InstantiationException,IllegalAccessException,IOException,ClassNotFoundException{//触发Singleton2类的初始化,会为类的静态变量赋予正确的初始值,单例对象就会被创建!Singleton2.otherMethod();System.out.println("-----------------------------------");System.out.println(Singleton2.getInstance());System.out.println(Singleton2.getInstance());}}//输出:privateSingleton2()otherMethod()[email protected][email protected]

可以看出当调用

otherMethod()

时,就会触发类的加载,枚举对象就会创建,所以枚举实现单例是饿汉式的

2.2 破坏单例

枚举类实现单例的好处:

  1. 反序列化无法破坏枚举单例
  2. 反射无法破坏枚举单例

栗子:

需要先修改反射破坏代码,枚举需要有参构造

publicclassTestSingleton{publicstaticvoidmain(String[] args)throwsException{Singleton5.otherMethod();System.out.println(">>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>");System.out.println(Singleton5.getInstance());System.out.println(Singleton5.getInstance());//反序列化破坏单例serializable(Singleton2.getInstance());//Unsafe破坏单例unsafe(Singleton2.class);//反射破坏单例reflection(Singleton2.class);}privatestaticvoidunsafe(Class<?> clazz)throwsInstantiationException{Object o =UnsafeUtils.getUnsafe().allocateInstance(clazz);System.out.println("Unsafe 创建实例:"+ o);}privatestaticvoidserializable(Object instance)throwsIOException,ClassNotFoundException{ByteArrayOutputStream bos =newByteArrayOutputStream();ObjectOutputStream oos =newObjectOutputStream(bos);
        oos.writeObject(instance);ObjectInputStream ois =newObjectInputStream(newByteArrayInputStream(bos.toByteArray()));System.out.println("反序列化创建实例:"+ ois.readObject());}privatestaticvoidreflection(Class<?> clazz)throwsNoSuchMethodException,InstantiationException,IllegalAccessException,InvocationTargetException{Constructor<?> constructor = clazz.getDeclaredConstructor(String.class,int.class);
        constructor.setAccessible(true);System.out.println("反射创建实例:"+ constructor.newInstance());}}

结果:

在这里插入图片描述

可以看出

  1. 反射是无法创建枚举对象!无法破坏枚举单例
  2. 反序列化也不会破坏枚举单例!
  3. Unsafe依然会破坏!

3.懒汉式

实现代码如下:

/**
 * 懒汉式
 *
 * @author xppll
 * @date 2021/12/25 08:34
 */publicclassSingleton3implementsSerializable{//构造私有privateSingleton3(){System.out.println("private Singleton3()");}//唯一实例privatestaticSingleton3 INSTANCE =null;publicstaticSingleton3getInstance(){//第一次调用的时候才创建if(INSTANCE ==null){
            INSTANCE =newSingleton3();}return INSTANCE;}//其他方法publicstaticvoidotherMethod(){System.out.println("otherMethod()");}}

测试:

/**
 * @author xppll
 * @date 2021/12/24 21:28
 */publicclassTestSingleton{publicstaticvoidmain(String[] args)throwsInvocationTargetException,NoSuchMethodException,InstantiationException,IllegalAccessException,IOException,ClassNotFoundException{Singleton3.otherMethod();System.out.println("-----------------------------------");System.out.println(Singleton3.getInstance());System.out.println(Singleton3.getInstance());}}

结果:

在这里插入图片描述

可以看出只有在第一次调用

getInstance()

时才会创建唯一的单例对象,因此是懒汉式的。

但是这种方式在多线程环境下是会有问题的,可能多个线程会同时执行

INSTANCE = new Singleton3();

。因此这里需要在

getInstance()

方法上加上

synchronized

关键字保证多线程下的正确性:

publicstaticsynchronizedSingleton3getInstance(){//第一次调用的时候才创建if(INSTANCE ==null){
        INSTANCE =newSingleton3();}return INSTANCE;}

但是这种方法是有问题的,第一次创建完对象后,以后的操作是不需要在加锁的,所以这种方式会影响性能!

我们的目标应该是第一次创建单例的时候给予保护,后续操作则不需要加锁保护!

4.双检锁懒汉式

针对上面的问题,这里给出第四种方法双检锁懒汉式进行优化:

/**
 * 双检锁懒汉式
 *
 * @author xppll
 * @date 2021/12/25 08:53
 */publicclassSingleton4{//构造私有privateSingleton4(){System.out.println("private Singleton4()");}//唯一实例//这里volatile的作用是保证共享变量有序性!privatestaticvolatileSingleton4 INSTANCE =null;//双检锁优化publicstaticsynchronizedSingleton4getInstance(){//实例没创建,才会进入内部的 synchronized 代码块,提高性能,防止每次都加锁if(INSTANCE ==null){//可能第一个线程在synchronized 代码块还没创建完对象时,第二个线程已经到了这一步,所以里面还需要加上判断synchronized(Singleton4.class){//也许有其他线程已经创建实例,所以再判断一次if(INSTANCE ==null){
                    INSTANCE =newSingleton4();}}}return INSTANCE;}//其他方法publicstaticvoidotherMethod(){System.out.println("otherMethod()");}}

关于这里的双检锁判断和volatile的使用可以看看我的这篇文章4-3节 :double-checked locking 问题

5.内部类懒汉式

内部类懒汉式单例实现:

/**
 * 内部类懒汉式
 *
 * @author xppll
 * @date 2021/12/25 09:24
 */publicclassSingleton5{//构造私有privateSingleton5(){System.out.println("private Singleton5()");}//静态内部类实现懒汉式单例,静态变量的创建会放在静态代码块里执行,jvm会保证其线程安全//只有第一次用到内部类时,才会初始化创建单例privatestaticclassHolder{staticSingleton5 INSTANCE =newSingleton5();}//获得实例方法publicstaticSingleton5getInstance(){returnHolder.INSTANCE;}//其他方法publicstaticvoidotherMethod(){System.out.println("otherMethod()");}}

测试:

/**
 * @author xppll
 * @date 2021/12/24 21:28
 */publicclassTestSingleton{publicstaticvoidmain(String[] args)throwsInvocationTargetException,NoSuchMethodException,InstantiationException,IllegalAccessException,IOException,ClassNotFoundException{Singleton5.otherMethod();System.out.println("-----------------------------------");System.out.println(Singleton5.getInstance());System.out.println(Singleton5.getInstance());}}

结果:

在这里插入图片描述

可以看出内部类实现单例也是懒汉式的

6.JDK中单例的体现

Runtime 体现了饿汉式单例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-N3tL76fL-1640434813190)(单例模式详解.assets/image-20211225094117525.png)]

System类下的Console 体现了双检锁懒汉式单例

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-B9vf09sf-1640434813191)(单例模式详解.assets/image-20211225094222659.png)]

Collections 中的 EmptyNavigableSet内部类懒汉式单例

在这里插入图片描述

Collections 中的ReverseComparator.REVERSE_ORDER 内部类懒汉式单例

在这里插入图片描述

Comparators.NaturalOrderComparator.INSTANCE 枚举饿汉式单例

在这里插入图片描述

在这里插入图片描述


本文转载自: https://blog.csdn.net/qq_45966440/article/details/122148123
版权归原作者 LL.LEBRON 所有, 如有侵权,请联系我们删除。

“【设计模式】单例模式”的评论:

还没有评论