设计模式:单例的五种实现及分析


    单例模式是设计模式中最简单的一种创建型模式,使用场景一般有:工具类对象、系统中只能存在一个实例对象的类、创建频繁或又耗时耗资源且又经常用到的对象等。如:JDK的Runtime类就是饥饿的单例模式,以及Spring容器管理的实例Bean默认也是饥饿单例,在容器启动时初始化,当然也可以设置为懒汉式(default-lazy-init="true")。再如程序中引入公共线程池,为防止多次创建线程池浪费资源,公共线程池也可以采用单例模式实现的。

饥饿模式

    类加载时默认初始化,实现最简单但也有一定的局限性,1、可能类加载时某些资源还未准备好,2、多个不同的自定义类加载器同时加载时可能导致重复初始化

public class HungrySingleton {

    private static final HungrySingleton instance = new HungrySingleton ();

    private HungrySingleton () {
        // 一定要记得私有化构造器
    }

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

懒汉模式(要注意线程安全问题)

public class LazySingleton {

    private static LazySingleton instance = null;

    private LazySingleton () { }

    /**
     * 一定要记得加synchronized
     */
    public static synchronized LazySingleton getInstance() {
        if(null == instance) {
            instance = new LazySingleton ();
        }
        return instance;
    }
}

双重检测锁模式

要注意线程安全隐患:多线程环境下获取到未初始化完全的实例对象,用volatile修饰对象可以防止该问题,详尽分析见下面注释

public class DoubleCheckSingleton {

    /**
     * TODO 这里主要利用了volatile的“顺序性”,保证对instance的写不会发生指令重排而引发其他线程获取到未初始化完全的对象
     * TODO 就算没有volatile,“可见性”也可由synchronized保证
     */
    private static volatile DoubleCheckSingleton instance = null;

    private DoubleCheckSingleton() { }

    public static DoubleCheckSingleton getInstance() {
        if (null == instance) {
            synchronized (DoubleCheckSingleton.class) {
                if (null == instance) {
                    // new DoubleCheckSingleton() 不是原子操作,大体步骤如下
                    // memory = allocate();    // 1:分配对象的内存空间
                    // ctorInstance(memory);   // 2:初始化对象
                    // instance = memory;      // 3:设置instance指向刚才分配的内存地址
                    // 伪代码中的2和3之间,可能会被重排序,所以如果执行顺序按1,3,2的话,线程在执行完3后时间片用完切换线程则可能出现未初始化的对象
                    // 如果用volatile来修饰则会产生写读屏障storeload,禁止2,3的指令重拍,从而防止出现未初始化完全的问题
                    // TODO 就算时间片用完切换其他线程也进入不了synchronized代码块,因为原来代码块还没执行完成,所以不会释放锁,也不会出现多次实例化
                    instance = new DoubleCheckSingleton();
                }
            }
        }
        return instance;
    }

静态内部类模式(推荐使用:由JVM保证线程安全且代码简单不容易出错)

public class InnerClassSingleton {

    private InnerClassSingleton() { }

    private static class NestClass {
        private static final InnerClassSingleton instance = new InnerClassSingleton();
    }

    public static InnerClassSingleton getInstance() {
        // javac编译后,NestClass是一个单独的.class文件,
        // 加载InnerClassSingleton.class的时候不会自动加载NestClass.class文件
        // 当调用NestClass.instance的时候才会触发JVM加载NestClass类
        return NestClass.instance;
    }
}

枚举模式

跟静态内部类的原理是一样的,JVM会保证enum不能被反射并且构造器方法只执行一次,因此该单例是线程安全的

public class EnumSingleton {

    private EnumSingleton() { }

    private enum InnerEnum {
        /**
         * 占位枚举值
         */
        enumFactory;
        private EnumSingleton instance;

        /**
         * TODO 注意这里是枚举类的构造器
         */
        private InnerEnum() {
            instance = new EnumSingleton();
        }
        public EnumSingleton getInstance() {
            return instance;
        }
    }

    public static EnumSingleton getInstance() {
        return InnerEnum.enumFactory.getInstance();
    }
}

作者:ocean.wen,发布于:2019/08/15
原文:https://www.cnblogs.com/ocean234/p/11176954.html