设计模式——单例模式实现

前言

单例模式的写法有很多种,这里线程安全的会有√标记,线程不安全的会指明问题。主要是在看完JVM虚拟机之后,从类加载过程、对象产生过程以及并发重排序的角度来分析问题,解释单例模式的几种写法正确与否。
主要看源代码上的注释分析,目前本文包括的单例模式写法包括:

  • 饿汉式
  • 懒汉式-同步方法
  • 懒汉式-双重校验
  • 懒汉式-静态内部类
  • 懒汉式-枚举语法糖

后续还有别的写法,会继续补充…

代码

public class Singleton{

    static class Instance {}

    /**
     * 【√饿汉式】
     * 类加载时在准备阶段分配内存,也就是instance = null,初始化阶段堆中创建Instance对象,该对象引用赋值给instance
     * 这样类加载完成时对象已经创建好,不存在问题
     */
    public static class EagerInitialization {
        public static Instance instance = new Instance();

        public static Instance getInstance() {
            return instance;
        }
    }

    /**
     * 【懒汉式-未同步】
     * 类加载完成后仍然是null,在getInstance()时才初始化
     * 这种写法在并发下是存在问题的,假如线程A,B同时调用getInstance(),
     * 线程A先执行到1,判断没有instance == null,然后突然停了换B,
     * 此时B也判断instance == null,那么B创建一个对象返回。A继续执行2,又创建了一个对象!!!!
     * 还单例个啥哦....
     */
    public static class LazyInitializationUnSync {
        public static Instance instance = null;

        public static Instance getInstance() {
            if (instance == null) {             // 1
                instance = new Instance();      // 2
            }
            return instance;
        }
    }

    /**
     * 【√懒汉式-同步方法】
     * 类加载完成后仍然是null,在getInstance()时才初始化
     * 假如线程A,B要同时调用getInstance(),因为是synchronized同步方法,互斥情况下只能一个线程持有锁,
     * 例如A先持有锁,创建完对象返回,B再进入同步方法发现有对象了,直接返回即可。
     * 该方法问题在于,每一次调用getInstance()都有一个获取锁的过程,性能降低了
     */
    public static class LazyInitializationSync {
        public static Instance instance = null;

        public static synchronized Instance getInstance() {
            if (instance == null) {
                instance = new Instance();
            }
            return instance;
        }
    }

    /**
     * 【懒汉式-同步代码块-尝试优化】
     * 假设有线程A,B同时调用getInstance(),线程A执行完1,然后咔,停了,线程B就执行完1,获取锁进入同步块,创建完对象赋给instance
     * 释放锁后在5返回前,线程切换回A,此时A进入同步块,有了再一次的判断,A将不再创建释放锁了。
     * 后面再来的线程,直接判断就好,不需要加锁。表面上没什么问题,一切都是那么和谐。
     * 其实存在下面这种情况,线程A执行到4,线程B同时开始执行1,A执行4包括:
     *      1). 先在堆中分配内存,设置对象头
     *      2). 然后对象初始化
     *      3). 最后赋值给instance
     * 在某些JIT编译器中,允许2)和3)重排序,所以可能先执行3)后执行2)。那么当A执行完3)执行2)之前切换成B,B判断instance不为null
     * 直接返回,此时返回的就是仅仅零值初始化的对象,还没有执行过用户初始化——<init>方法
     */
    public static class LazyInitializationSyncWantBetter {
        public static Instance instance = null;

        public static Instance getInstance() {
            if (instance == null) {                                             // 1
                synchronized (LazyInitializationSyncWantBetter.class) {         // 2
                    if (instance == null) {                                     // 3
                        instance = new Instance();                              // 4
                    }
                }
            }
            return instance;                                                    // 5
        }
    }

    /**
     * 【√懒汉式-同步代码块-正确优化】
     * 对上面优化的思路很简单,主要就是:
     *      1. 禁止2)和3)重排序;
     *      2. 让B线程看不到重排序的过程。
     * 将instance声明为volatile,可以使得4操作中的步骤3),volatile的写指令前后插入内存屏障,不允许进行重排序
     */
    public static class LazyInitializationSyncTrueBetter {
        public static volatile Instance instance = null;

        public static Instance getInstance() {
            if (instance == null) {                                             // 1
                synchronized (LazyInitializationSyncTrueBetter.class) {         // 2
                    if (instance == null) {                                     // 3
                        instance = new Instance();                              // 4
                    }
                }
            }
            return instance;                                                    // 5
        }
    }


    /**
     * 【√懒汉式-JVM的类加载机制实现】
     * 当有线程A,B同时调用getInstance()方法,发现需要加载InstanceHolder类
     * JVM的类加载机制利用锁会保证类只被加载一次,所以当类加载的初始化阶段会执行<cinit>方法,会创建出一个对象赋给instance
     * 最终线程A,B都获取到一个单例的instance
     */
    public static class LazyInitializationJvmSync {

        public static class InstanceHolder {
            public static Instance instance = new Instance();
        }

        public static Instance getInstance() {
            return InstanceHolder.instance;
        }
    }

    /**
     * 【√懒汉式-枚举的语法糖】
     * enum的语法糖实现,本质上instance其实是Instance的一个静态常量,Instance是继承自Enum类的一个子类
     * instance在Instance子类的静态代码块中进行初始化,由于instance是静态常量,但并不是基本数据类型或String
     * 所以仍然是在<cinit>中进行初始化,而不是在类加载的准备阶段初始化
     * 可以通过反编译查看enum语法糖的本质
     */
    public static class LazyInitializationEnum {

        public enum Instance {
            instance
        }

        public static Instance getInstance() {
            return Instance.instance;
        }
    }
}
©️2020 CSDN 皮肤主题: 数字20 设计师:CSDN官方博客 返回首页