设计模式详解之单例模式

设计模式详解之单例模式


前言

提示:软件设计模式(Design pattern),又称设计模式,是一套被反复使用、多数人知晓的、经过分类编目的、代码设计经验的总结。使用设计模式是为了可重用代码、让代码更容易被他人理解、保证代码可靠性、程序的重用性。


提示:以下是本篇文章正文内容,下面案例可供参考

一、懒汉式

  1. 相较于饿汉式,第一次获取对象时为空,需要创建对象。
  2. 由于使用了Double check和synchronized,对性能稍微有写损耗。
  3. 代码展示
/**
 * 懒汉模式
 * 1、volatile是为了保证新创建的对象可见和防止指令重排序
 *  //字节码层面
 *    1、分配空间
 *    2、初始化
 *    3、引用复制
 *    如果不加volatile,第二步和第三步的顺序可以是不固定的。如果不固定有可能在第二检查对象是否未空时出现问题。
 *    若第二步和第三步顺序反了,第一个线程创建好对象,先复制,后一个线程获取到锁,判断到对象为空,对象将会被改变。
 * 2、双重检查避免锁的问题
 */
public class LazySingleton implements Serializable {
    static final long serialVersionUID = 42L;
    private volatile static LazySingleton instance;

    private LazySingleton(){}

    public static LazySingleton getInstance() {
        if (instance == null) {
            synchronized(LazySingleton.class) {
                /**
                 * 1、再此检查是为了防止并发
                 * 如果多个线程刚开始都在等待锁,一个线程获取到锁之后对象实例化完成,
                 * 后面线程获取到线程会继续实例化,会导致对象不一致问题。
                 */
                if ( instance == null) {
                    instance = new LazySingleton();
                }
            }
        }
        return instance;
    }

    public Object readResolve() throws ObjectStreamException {
        return instance;
    }
}

class LazySingletonTest {
    public static void main(String[] args) throws Exception {
        test2();
    }

    /**
     * 如果需要保证反序列化后的对象和单例对象一致
     * 1、首先对象实现Serializable接口
     * 2、写一个Object readResolve() throws ObjectStreamException方法
     * @throws Exception
     */
    public static void test3() throws Exception {
        LazySingleton instance = LazySingleton.getInstance();

        //将实例化的对象保存到磁盘中
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("LazySingleton"));
        oos.writeObject(instance);

        //将磁盘中的对象转为实例对象
        ObjectInputStream ios = new ObjectInputStream(new FileInputStream("LazySingleton"));
        LazySingleton object = ((LazySingleton) ios.readObject());

        //判断两个对象是否为同一个对象
        System.out.println(instance == object);
    }

    /**
     * 测试反射情况
     * 懒汉模式无法保证反射对象与单例对象是同一个对象
     */
    public static void test2() throws Exception {
        LazySingleton instance = LazySingleton.getInstance();
        Constructor<LazySingleton> constructor = LazySingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        LazySingleton lazySingleton = constructor.newInstance();
        //判断懒汉模式能否防止反射获取对象
        System.out.println(instance == lazySingleton);
    }

    /**
     * 测试并发、指令重排问题
     */
    public static void test1() {
        List<LazySingleton> lists = new ArrayList<>();
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        for (int i=0; i<20; i++) {
            executorService.execute(() -> {
                LazySingleton instance = LazySingleton.getInstance();
                System.out.println(instance);
                if (!lists.contains(instance)) {
                    lists.add(instance);
                }
            });
        }
        executorService.shutdown();
        System.out.println(lists.size());
        lists.forEach(lazySingleton -> System.out.println(lazySingleton));
    }
}

二、饿汉模式

  1. 饿汉模式在初始化对象的时候就已经实例化好对象了,相较于懒汉模式,性能会高一些。
  2. 代码展示
/**
 * 饿汉模式
 * 类加载的 初始化阶段就完成了 实例的初始化。本质上就是借助于JVM类加载机制,保证实例的唯一性。
 * 类加载过程:
 * 1、加载二进制数据到内存中,生成对应的Class数据结构。
 * 2、连接:a.验证,b.准备(给类的静态成员变量赋默认值),c.解析
 * 3、初始化:给类的静态变量赋初值
 * 只有在真正使用对应的类时,才会出发初始化(当前类是启动类即main函数所在类,直接进行new操作,访问静态属性、访问静态方法,用反射访问类,初始化一个类的子类等)
 */
public class HungrySingleton implements Serializable {
    static final long serialVersionUID = 42L;
    private static HungrySingleton instance = new HungrySingleton();

    private HungrySingleton() {
    }

    public static HungrySingleton getInstance() {
        //防止通过反射方式创建对象
        if (instance !=null) {
            throw new RuntimeException("单例模式下不允许有多个对象");
        }
        return instance;
    }

    public Object readResolve() throws ObjectStreamException {
        return instance;
    }
}

class HungrySingletonTest {
    public static void main(String[] args) throws Exception {
        test2();
    }

    /**
     * 测试反序列化获取对象
     * 可以保证反序列化和单例对象一致
     */
    public static void test3() throws Exception {
        HungrySingleton instance = HungrySingleton.getInstance();

        //序列化对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("HungrySingleton"));
        oos.writeObject(instance);

        //反序列化对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("HungrySingleton"));
        HungrySingleton hungrySingleton = (HungrySingleton) ois.readObject();

        System.out.println(instance == hungrySingleton);
    }

    /**
     * 测试反射获取单例对象
     * 无法保证
     */
    public static void test2() throws Exception {
        HungrySingleton instance = HungrySingleton.getInstance();
        Constructor<HungrySingleton> constructor = HungrySingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        HungrySingleton hungrySingleton = constructor.newInstance();
        System.out.println(hungrySingleton == instance);
    }

    /**
     * 测试并发情况下
     */
    public static void test1() {
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        for (int i = 0; i < 20; i++) {
            executorService.execute(() -> {
                HungrySingleton instance = HungrySingleton.getInstance();
                System.out.println(instance);
            });
        }
        executorService.shutdown();
    }
}

三、静态内部类

  1. 代码展示

/**
 * 静态内部类单例对象
 */
public class StaticInnerClassSingleton implements Serializable {
    static final long serialVersionUID = 42L;

    private StaticInnerClassSingleton(){}

    public static StaticInnerClassSingleton getInstance() {
        if (innerStaticInnerClassSinleton.instance != null) {
            //防止反射来创建对象
            throw new RuntimeException("单例不允许多个实例");
        }
        return innerStaticInnerClassSinleton.instance;
    }

    private static class innerStaticInnerClassSinleton{
        private static StaticInnerClassSingleton instance = new StaticInnerClassSingleton();
    }
    public Object readResolve() throws ObjectStreamException{
        return innerStaticInnerClassSinleton.instance;
    }
}


class StaticInnerSingletonTest{
    public static void main(String[] args) throws Exception {
        test2();
    }


    /**
     * 静态内部类可以保证反序列化后的对象和单例对象一致
     * @throws Exception
     */
    public static void test3() throws Exception {
        StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();

        //序列化对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("StaticInnerClassSingleton"));
        oos.writeObject(instance);

        //反序列化对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("StaticInnerClassSingleton"));
        StaticInnerClassSingleton staticInnerClassSingleton = (StaticInnerClassSingleton) ois.readObject();

        System.out.println(instance == staticInnerClassSingleton);
    }

    /**
     * 测试反射情况
     * 静态内部类无法保证反射对象与单例对象一至
     * @throws Exception
     */
    public static void test2() throws Exception{
        StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();

        Constructor<StaticInnerClassSingleton> constructor = StaticInnerClassSingleton.class.getDeclaredConstructor();
        constructor.setAccessible(true);
        StaticInnerClassSingleton staticInnerClassSingleton = constructor.newInstance();
        System.out.println(instance == staticInnerClassSingleton);
    }

    /**
     * 并发情况
     */
    public static void test1() {
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        for (int i=0; i< 20; i++) {
            executorService.execute(()->{
                StaticInnerClassSingleton instance = StaticInnerClassSingleton.getInstance();
                System.out.println(instance);
            });
        }
        executorService.shutdown();
    }
}

四、枚举类型

  1. 代码展示
public enum  EnumSingleton{
    INSTANCE;

    public void print() {
        System.out.println(INSTANCE.hashCode());
    }
}

class EnumSingletonTest{
    public static void main(String[] args) throws Exception {
        test3();
    }

    //枚举类型可以保证反序列化的对象和单例对象一致
    public static void test3() throws Exception {
        EnumSingleton instance = EnumSingleton.INSTANCE;

        //序列化对象
        ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream("EnumSingleton"));
        oos.writeObject(instance);

        //反序列化对象
        ObjectInputStream ois = new ObjectInputStream(new FileInputStream("EnumSingleton"));
        EnumSingleton enumSingleton = (EnumSingleton) ois.readObject();

        System.out.println(enumSingleton == instance);
    }

    //枚举类型无法通过反射创建单例对象
    public static void test2() throws Exception {
        EnumSingleton instance = EnumSingleton.INSTANCE;
        Constructor<EnumSingleton> constructor = EnumSingleton.class.getDeclaredConstructor(String.class, int.class);
        constructor.setAccessible(true);
        constructor.newInstance("INSTANCE", 0);
    }

    //测试并发情况
    public static void test1() {
        ExecutorService executorService = Executors.newFixedThreadPool(20);
        for (int i=0; i<20;i++) {
            executorService.execute(()->{
                EnumSingleton.INSTANCE.print();
            });
        }
        executorService.shutdown();
    }
}

五、四种方式创建单例对象对比

懒汉模式饿汉模式静态内部类模式枚举模式
并发情况
反射获取对象
反序列化获取对象
  1. 懒汉模式通过public Object readResolve() 来保证反射的对象和单例对象一致.
  2. 饿汉模式、静态内部类模式、枚举模式都是借助JVM来保证并发情况下对象一致。

总结

以上就是个人对设计模式中的单例模式的详解,有不足或错误的地方望在评论区指出。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值