GOF23 单例模式

单例模式

优点

  1. 单例模式可以保证内存中只有一个实例,减少了内存的开销
  2. 可以避免对资源的多重占用
  3. 单例模式设置全局访问点,可以优化和共享资源的访问

缺点

  1. 单例模式一般没有接口,扩展困难。如果要扩展,则除了修改原来的代码,没有第二种途径,违背开闭原则
  2. 在并发测试中,单例模式不利于代码调试。在调试的过程中,如果单例中的代码没有执行完,也不能模拟生成一个新的对象
  3. 单例模式的功能代码通常写在一个类中,如果功能设计不合理,则很容易违背单一职责。

饿汉式单例

缺点

当存在大量的单例对象的时候,而且单例的数量不能确定,则系统初始化过程中会造成资源浪费,从而导致系统内存不足

// 饿汉式单例
public class Hungry {
    // 可能会浪费空间
    private byte[] data1 = new byte[1024 * 1024];
    private byte[] data2 = new byte[1024 * 1024];
    private byte[] data3 = new byte[1024 * 1024];
    private byte[] data4 = new byte[1024 * 1024];

    private Hungry() {
    }

    private final static Hungry HUNGRY = new Hungry();

    public static Hungry getInstance() {
        return HUNGRY;
    }
}

懒汉式单例 DCL

双重检查锁单例

public class LazyMan {
    private volatile static LazyMan lazyMan; //禁止指令重排
    private LazyMan(){
        System.out.println(Thread.currentThread().getName());
    }
    public static LazyMan getInstance(){
        // 双重检测锁模式的 懒汉式单例 DCL懒汉式
        if (lazyMan==null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan(); //不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) {
        //测试是否为单例对象
        for (int i = 0; i <10 ; i++) {
            new Thread(()->{
                getInstance();
            }).start();
        }
    }

}

通过反射初步破坏单例模式

public class LazyMan {
    private volatile static LazyMan lazyMan; //禁止指令重排
    private LazyMan(){
        //加锁判断lazyMan是否存在 存在的话抛出异常
        synchronized(LazyMan.class){
            if (lazyMan!=null){
                throw new RuntimeException("不要通过反射破坏");
            }
        }
    }
    public static LazyMan getInstance(){
        // 双重检测锁模式的 懒汉式单例 DCL懒汉式
        if (lazyMan==null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan(); //不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        constructor.setAccessible(true);
        LazyMan lazyMan = getInstance();
        LazyMan lazyMan1 = constructor.newInstance();
        System.out.println(lazyMan);
        System.out.println(lazyMan1);
    }

}

image-20201129100324280

继续再次破坏单例模式

image-20201129100841857

image-20201129100948231

道高一尺 魔高

public class LazyMan {
    private volatile static LazyMan lazyMan; //禁止指令重排
    private volatile static boolean zjh=false; // 添加一个判断字段
    private LazyMan(){
        synchronized(LazyMan.class){
            if (!zjh){
                zjh=true;
            }else{
                throw new RuntimeException("不要试图通过反射破坏单例模式");
            }
        }
    }
    public static LazyMan getInstance(){
        // 双重检测锁模式的 懒汉式单例 DCL懒汉式
        if (lazyMan==null) {
            synchronized (LazyMan.class) {
                if (lazyMan == null) {
                    lazyMan = new LazyMan(); //不是一个原子性操作
                }
            }
        }
        return lazyMan;
    }

    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
        Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
        Field zjh = LazyMan.class.getDeclaredField("zjh"); //获取zjh字段
        constructor.setAccessible(true); //强制
//        LazyMan lazyMan = getInstance();
        zjh.setAccessible(true);
        LazyMan lazyMan1 = constructor.newInstance();
        zjh.set(lazyMan1,false); //设置字段的值为false
        LazyMan lazyMan = constructor.newInstance();
        System.out.println(lazyMan);
        System.out.println(lazyMan1);
    }

}
/
**
* 1. 分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
* *
123
* 132 A
* B // 此时lazyMan还没有完成构造
*/

静态内部类

public class Holder {
    private Holder(){
        
    }

    public static Holder getInstance(){
        return InnerClass.HOLDER;
    }
    public static class InnerClass{
        private static final Holder HOLDER=new Holder();
    }
}
  • 单例不安全,可以通过反射破解

枚举

枚举式单例写法

public enum EnumSingleton implements Serializable {
    INSTALL;
    private Object data;

    public Object getData() {
        return data;
    }

    public void setData(Object data) {
        this.data = data;
    }

    public static EnumSingleton getInstance(){
        return INSTALL;
    }
}
  • 测试
public class EnumSingletonTest {

    public static void main(String[] args) {
        try{
            EnumSingleton instance1=null;
            EnumSingleton instance2=EnumSingleton.getInstance();
            instance2.setData(new Object());

            FileOutputStream fileOutputStream=new FileOutputStream("EnumSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream);
            oos.writeObject(instance2);
            oos.flush();
            oos.close();

            FileInputStream fileInputStream=new FileInputStream("EnumSingleton.obj");
            ObjectInputStream oos2 = new ObjectInputStream(fileInputStream);
            instance1= (EnumSingleton) oos2.readObject();
            oos2.close();
            System.out.println(instance1.getData());
            System.out.println(instance2.getData());
            System.out.println(instance1.getData()==instance2.getData());
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  • 结果

image-20210321141120020

  • 这根我们预期的结果不太一样
// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingleton.java
package 521B5EFA578B6A215F0F.53554F8B6A215F0F;
import java.io.Serializable;

public final class EnumSingleton extends Enum
    implements Serializable
{

    public static EnumSingleton[] values()
    {
        return (EnumSingleton[])$VALUES.clone();
    }
	//toString的逆方法,返回指定名字,给定类的枚举常量
    public static EnumSingleton valueOf(String name)
    {
        return (EnumSingleton)Enum.valueOf(521B5EFA578B6A215F0F/53554F8B6A215F0F/EnumSingleton, name);
    }
	//私有构造函数,参数有 此枚举常量的名称,枚举常量的序号
    private EnumSingleton(String s, int i)
    {
        super(s, i);
    }

    public Object getData()
    {
        return data;
    }

    public void setData(Object data)
    {
        this.data = data;
    }

    public static EnumSingleton getInstance()
    {
        return INSTALL;
    }
	//单例对象的名称
    public static final EnumSingleton INSTALL;
    //单例对象的属性
    private Object data;
    //枚举类中的所有值 包装到values数组中
    private static final EnumSingleton $VALUES[];
    static 
    {
        //与饿汉式相似,类初始化时创建单例对象  指定名称和枚举常量的序号
        INSTALL = new EnumSingleton("INSTALL", 0);
        $VALUES = (new EnumSingleton[] {
            INSTALL
        });
    }
}

枚举式单例写法在静态块中就对INSTALL赋值了,是饿汉式的实现

  • 至此我们还可以想序列化是否可以破解枚举的单例 ,现在回到ObjectInputStream的readObject0()方法。

image-20210321143811843

  • 接下来我们看readEnum的代码实现
/**
     * Reads in and returns enum constant, or null if enum type is
     * unresolvable.  Sets passHandle to enum constant's assigned handle.
     */
    private Enum<?> readEnum(boolean unshared) throws IOException {
        if (bin.readByte() != TC_ENUM) {
            throw new InternalError();
        }

        ObjectStreamClass desc = readClassDesc(false);
        if (!desc.isEnum()) {
            throw new InvalidClassException("non-enum class: " + desc);
        }

        int enumHandle = handles.assign(unshared ? unsharedMarker : null);
        ClassNotFoundException resolveEx = desc.getResolveException();
        if (resolveEx != null) {
            handles.markException(enumHandle, resolveEx);
        }

        String name = readString(false);
        Enum<?> result = null;
        Class<?> cl = desc.forClass();
        if (cl != null) {
            try {
                @SuppressWarnings("unchecked")
                Enum<?> en = Enum.valueOf((Class)cl, name);
                result = en;
            } catch (IllegalArgumentException ex) {
                throw (IOException) new InvalidObjectException(
                    "enum constant " + name + " does not exist in " +
                    cl).initCause(ex);
            }
            if (!unshared) {
                handles.setObject(enumHandle, result);
            }
        }

        handles.finish(enumHandle);
        passHandle = enumHandle;
        return result;
    }

​ 由上可知,枚举类型其实通过类名和类对象找到唯一的枚举对象。因此,枚举对象不可能被类加载器加载两次。

试图通过反射破坏枚举的单例模式

public enum EnumSingle {
    INSTANCE;
    private EnumSingle getInstance(){
        return INSTANCE;
    }


}

class Test01{
    public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
        Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
        declaredConstructor.setAccessible(true);
        EnumSingle enumSingle = declaredConstructor.newInstance();
        System.out.println(enumSingle);
    }
}
  • 显示没有无参构造方法 而不是显示不能通过反射调用

image-20201129221127585

再次通过反射破解

public enum EnumSingle {
    INSTANCE;
    private EnumSingle getInstance(){
        return INSTANCE;
    }
}

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

image-20201129220930974

查看newInstance源码

@CallerSensitive
public T newInstance(Object ... initargs)
    throws InstantiationException, IllegalAccessException,
           IllegalArgumentException, InvocationTargetException
{
    if (!override) {
        if (!Reflection.quickCheckMemberAccess(clazz, modifiers)) {
            Class<?> caller = Reflection.getCallerClass();
            checkAccess(caller, clazz, null, modifiers);
        }
    }
    //判断是否为枚举类 
    if ((clazz.getModifiers() & Modifier.ENUM) != 0)
        //如果是直接抛出异常
        throw new IllegalArgumentException("Cannot reflectively create enum objects");
    ConstructorAccessor ca = constructorAccessor;   // read volatile
    if (ca == null) {
        ca = acquireConstructorAccessor();
    }
    @SuppressWarnings("unchecked")
    T inst = (T) ca.newInstance(initargs);
    return inst;
}

反编译源码

// Decompiled by Jad v1.5.8g. Copyright 2001 Pavel Kouznetsov.
// Jad home page: http://www.kpdus.com/jad.html
// Decompiler options: packimports(3) 
// Source File Name:   EnumSingle.java

package com.itheim.Demo14;


public final class EnumSingle extends Enum
{

    public static EnumSingle[] values()
    {
        return (EnumSingle[])$VALUES.clone();
    }

    public static EnumSingle valueOf(String name)
    {
        return (EnumSingle)Enum.valueOf(com/itheim/Demo14/EnumSingle, name);
    }

    private EnumSingle(String s, int i)
    {
        super(s, i);
    }

    public EnumSingle getInstance()
    {
        return INSTANCE;
    }

    public static final EnumSingle INSTANCE;
    private static final EnumSingle $VALUES[];

    static 
    {
        INSTANCE = new EnumSingle("INSTANCE", 0);
        $VALUES = (new EnumSingle[] {
            INSTANCE
        });
    }
}

还原反序列化破坏单例模式的事故现场

  • 序列化就是把内存中的状态通过转换为字节码的形式 从而转换为IO流,写入其他的地方(可以是磁盘、网络IO)内存中的状态会被永久保留下来
  • 反序列化就是把已经序列化的字节码内容转换为IO流 通过IO流的读取,进而将读取的内容转换为java对象在转换过程中重新创建对象
package 创建型模式.单例模式;

import java.io.*;

/**
 * @program: DesignPattern
 * @description:
 * @author: ZGrey
 * @create: 2021-03-21 13:53
 **/
public class SeriableSingleton implements Serializable {
    public final static SeriableSingleton INSTANCE=new SeriableSingleton();
    private SeriableSingleton(){}
    public static SeriableSingleton getInstance(){
        return INSTANCE;
    }
    private Object readResolve(){
        return INSTANCE;
    }
    public static void main(String[] args) {
        try{
            SeriableSingleton instance1=null;
            SeriableSingleton instance2=SeriableSingleton.getInstance();

            FileOutputStream fileOutputStream=new FileOutputStream("EnumSingleton.obj");
            ObjectOutputStream oos = new ObjectOutputStream(fileOutputStream);
            oos.writeObject(instance2);
            oos.flush();
            oos.close();

            FileInputStream fileInputStream=new FileInputStream("EnumSingleton.obj");
            ObjectInputStream oos2 = new ObjectInputStream(fileInputStream);
            instance1= (SeriableSingleton) oos2.readObject();
            oos2.close();
            System.out.println(instance1);
            System.out.println(instance2);
            System.out.println(instance1==instance2);
        } catch (IOException | ClassNotFoundException e) {
            e.printStackTrace();
        }
    }
}
  • 结果如图所示 发现并不是同一个对象实例

image-20210321145526747

  • 那么如何通过序列化实现单例模式呢 其实很简单 添加readResolve方法即可
public class SeriableSingleton implements Serializable {
    public final static SeriableSingleton INSTANCE=new SeriableSingleton();
    private SeriableSingleton(){}
    public static SeriableSingleton getInstance(){
        return INSTANCE;
    }
    private Object readResolve(){
        return INSTANCE;
    }
}    
  • 再次运行:

image-20210321145732075

原文地址:https://www.cnblogs.com/zgrey/p/14563047.html