zoukankan      html  css  js  c++  java
  • 八股文系列之单例模式

    单例模式概念

      1.什么是单例模式?

        保证整个系统中一个类只有一个对象的实例,实现这种功能的方式就叫单例模式。

      2.实现单例模式的思路

        1. 构造私有:

          如果要保证一个类不能多次被实例化,那么我肯定要阻止对象被new 出来,所以需要把类的所有构造方法私有化。

        2.以静态方法返回实例:

          因为外界就不能通过new来获得对象,所以我们要通过提供类的方法来让外界获取对象实例。

        3.确保对象实例只有一个:

          只对类进行一次实例化,以后都直接获取第一次实例化的对象。

      1.饿汉式

    /**
     * 单例模式
     * 饿汉式
     */
    public class Singleton1 implements Serializable {
        // 构造函数
        private Singleton1() {
            System.out.println("开始初始化实例======");
        }
    ​
        private final static Singleton1 instance = new Singleton1();
    ​
        public static Singleton1 getInstance() {
            return instance;
        }
    ​
        // 一个其他方法用来测试执行该方法时会不会实例化
        public static void otherMethod() {
            System.out.println("执行其他方法======");
        }
    ​
    }

      测试饿汉式执行代码,并且通过反射,反序列化,和unsafe类,测试是否可以破坏单例模式

    public class TestSingleton {
    ​
        public static void main(String[] args) throws Exception {
            Singleton1.otherMethod();
            System.out.println("=======================");
            System.out.println(Singleton1.getInstance());
            System.out.println(Singleton1.getInstance());
    ​
            reflection(Singleton1.class);
            serializable(Singleton1.getInstance());
            unsafe(Singleton1.class);
        }
    ​
    ​
        //通过反射破坏单例模式
        public static void reflection(Class<?> clazz) throws Exception {
            Constructor<?> constructor = clazz.getDeclaredConstructor();
            constructor.setAccessible(true);
            System.out.println("反射创建实例:"+constructor.newInstance());
        }
    ​
        //通过反序列化破坏实例
        public static void serializable(Object instance) throws Exception {
            ByteArrayOutputStream bos = new ByteArrayOutputStream();
            ObjectOutputStream oos = new ObjectOutputStream(bos);
            oos.writeObject(instance);
            ObjectInputStream ois = new ObjectInputStream(new ByteArrayInputStream(bos.toByteArray()));
            System.out.println("反序列化创建实例:"+ois.readObject());
        }
    ​
        //通过unsafe破坏单例
        public static void unsafe(Class<?> clazz) throws InstantiationException {
            Object instance = UnsafeUtils.getUnsafe().allocateInstance(clazz);
            System.out.println("unsafe 创建实例:"+instance);
        }
    }

      执行结果如下:

      可以看到确实在执行其他方法前,就初始化了该实例,并且也两次调用 getInstance() 方法也成功返回来同一实例,但是可以看到通过反射,反序列化,和unsafe类都可以破坏该单例模式,创建了不同的实例对象.

     

      优化饿汉式的代码,可以解决反射,反序列化的破坏,但是目前没有找到防止unsafe破坏的方法:

    /**
     * 单例模式
     * 饿汉式
     */
    public class Singleton1 implements Serializable {
        // 构造函数
        private Singleton1() {
            // 防止反射创建对象破坏单例
            if (instance != null) {
                throw new RuntimeException("单例对象不能重复创建");
            }
            System.out.println("开始初始化实例======");
        }
    ​
        private final static Singleton1 instance = new Singleton1();
    ​
        public static Singleton1 getInstance() {
            return instance;
        }
    ​
        // 一个其他方法用来测试执行该方法时会不会实例化
        public static void otherMethod() {
            System.out.println("执行其他方法======");
        }
    ​
        // 对于实现序列化的对象重写readResolve()方法,防止反序列化破幻单例
        public Object readResolve() {
            return instance;
        }
    }

      执行结果如下: 执行反射创建时直接抛出异常,反序列化返回的实例也都是同一个实例

     

     

      2.枚举饿汉式

    /**
     *  枚举饿汉式
     */
    public enum Singleton2 {
        INSTANCE;
    ​
        private Singleton2() {
            System.out.println("开始初始化实例======");
        }
    ​
        @Override
        public String toString() {
            return getClass().getName() + "@" + Integer.toHexString(hashCode());
        }
    ​
        public static Singleton2 getInstance() {
            return INSTANCE;
        }
    ​
        public static void otherMethod() {
            System.out.println("otherMethod()");
        }
    }

      执行结果如下: 枚举饿汉式能天然防止反射、反序列化破坏单例

     

     

      3.双检锁懒汉式

    /**
     * 双检锁懒汉式
     */
    public class Singleton3 implements Serializable {
        private Singleton3() {
            // 防止反射创建对象破坏单例
            if (INSTANCE != null) {
                throw new RuntimeException("单例对象不能重复创建");
            }
            System.out.println("开始初始化实例======");
        }
    ​
        private static volatile Singleton3 INSTANCE = null; // volatile 保证不同线程内存可见性,执行有序性
    public static Singleton3 getInstance() {
            if (INSTANCE == null) {
                // 加锁保证多线程并发执行
                synchronized (Singleton3.class) {
                    if (INSTANCE == null) {
                        INSTANCE = new Singleton3();
                    }
                }
            }
            return INSTANCE;
        }
    ​
        public static void otherMethod() {
            System.out.println("otherMethod()");
        }
    ​
        // 对于实现序列化的对象重写readResolve()方法,防止反序列化破幻单例
        public Object readResolve() {
            return INSTANCE;
        }
    }

      为何必须加 volatile:

    • INSTANCE = new Singleton3() 不是原子的,分成 3 步:创建对象、调用构造、给静态变量赋值,其中后两步可能被指令重排序优化,变成先赋值、再调用构造

    • 如果线程1 先执行了赋值,线程2 执行到第一个 INSTANCE == null 时发现 INSTANCE 已经不为 null,此时就会返回一个未完全构造的对象

      执行结果如下:

      可以看到在执行其他方法是并没有进行初始化实例,在调用 getInstance() 时才会进行初始化.

     

     

      4.内部类懒汉式

    /***
     * 内部类懒汉式
     */
    public class Singleton4 implements Serializable {
        private Singleton4() {
            // 防止反射创建对象破坏单例
            if (Holder.INSTANCE != null) {
                throw new RuntimeException("单例对象不能重复创建");
            }
            System.out.println("开始初始化实例======");
        }
    ​
        private static class Holder {
            static Singleton4 INSTANCE = new Singleton4();
        }
    ​
        public static Singleton4 getInstance() {
            return Holder.INSTANCE;
        }
    ​
        public static void otherMethod() {
            System.out.println("otherMethod()");
        }
    ​
        // 对于实现序列化的对象重写readResolve()方法,防止反序列化破幻单例
        public Object readResolve() {
            return Holder.INSTANCE;
        }
    }

      当外部类被访问时,并不会加载内部类,所以只要不访问Holder这个内部类,static Singleton4 INSTANCE = new Singleton4(); 不会实例化,这就相当于实现懒加载的效果,只有当Singleton4.getInstance() 被调用时访问内部类的属性,此时才会将对象进行实例化,这样既解决了饿汉模式下可能造成资源浪费的问题,也避免了了懒汉模式下的并发问题。

      执行结果如下:

     

      JDK 中单例的体现

    • Runtime 体现了饿汉式单例

    • Console 体现了双检锁懒汉式单例

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

    • ReverseComparator.REVERSE_ORDER 内部类懒汉式单例

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

  • 相关阅读:
    subprocess 子进程模块
    3.5 魔法方法
    ThinkPHP中,display和assign用法详解
    linux常用指令
    退出当前Mysql使用的db_name 的方式
    PHP中GD库是做什么用的? PHP GD库介绍11111111
    include跟include_once 以及跟require的区别
    全局变量跟局部变量
    关于define
    创建、删除索引---高级部分
  • 原文地址:https://www.cnblogs.com/xuxiaobai13/p/15465012.html
Copyright © 2011-2022 走看看