八股文系列之单例模式

单例模式概念

  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 枚举饿汉式单例

原文地址:https://www.cnblogs.com/xuxiaobai13/p/15465012.html