单例模式

作者: GeekerLou | 来源:发表于2020-03-08 09:55 被阅读0次

    单例模式

    单例模式确保一个类只有一个实例,并提供一个全局访问点。

    我们常常希望某个对象实例只有一个,不想要频繁地创建和销毁对象,浪费系统资源,最常见的就是 IO 、数据库的连接、Redis 连接等对象,完全没有必要创建多个一模一样的对象,一个足矣。

    实现思路:(俗称"两私一公开")

    • 私有静态实例(懒实例化/直接实例化)
    • 私有构造方法
    • 公开的静态获取方法

    要点:

    • 使用加锁或者静态变量、静态内部类、枚举等手段提供线程安全的保证
    • 突破线程安全的手段:反射

    典型实现

    • Hungry Singleton
    • Lazy Singleton
    • Double Checked Locking Singleton
    • Static Class Singleton
    • Enum Singleton

    使用场景

    • 需要频繁地创建和销毁,从而浪费系统资源的对象,例如:IO 、数据库的连接、Redis。使用单例模式能够有效地解决这一问题。

    实例

    SimpleSingleton

    /**
     * 非线程安全的简单的单例模式
     */
    @NotThreadSafe
    public class SimpleSingleton {
    
        private static SimpleSingleton instance;
    
        private SimpleSingleton(){}
    
        //当多个线程同时调用 getInstance() 方法时,可能会产生多个 instance 实例,因此这种方式并不是真正的单例。
        public static SimpleSingleton getInstance(){
            if(instance == null){
                instance = new SimpleSingleton();
            }
            return instance;
        }
    }
    
    

    LazySingleton

    /**
     * 懒汉式的单例模式:只在首次调用获得对象实例的方法时才会实例化对象
     *
     *
     * 优点:
     * 1. 实现了延迟加载
     *
     * 缺点:
     * 1. 使用延迟加载而引入同步关键字synchronized反而降低了系统的性能
     */
    @ThreadSafe
    public class LazySingleton {
        /**
         * 私有化的构造函数,确保单例不会再系统中的其他地方被实例化
         */
        private LazySingleton() {
            System.out.println("LazySingleton is creating");
        }
    
        /**
         * instance初始值被赋值null,确保系统启动时没有额外的负载
         */
        private static LazySingleton instance = null;
    
        /**
         * 首先判断当前单例是否已经存在,若存在则直接返回,不存在则新建后再返回新建的
         * synchronized关键字保证了多线程环境下instance == null判断的安全性
         *
         * @return
         */
        public static synchronized LazySingleton getInstance() {
            if (instance == null) {
                instance = new LazySingleton();
            }
            return instance;
        }
    }
    

    HungrySingleton

    /**
     * 饿汉式单例模式:在单例类被加载时候,就实例化一个对象交给自身管理的引用
     *
     * 优点:简单,可靠。
     *
     * 缺点:无法对instance实例做延迟加载。具体而言
     * 首次,在首次使用到该单例类时会进行类加载,该加载的过程可能会很慢,从而影响到应用的启动速度;
     * 其次,该单例类在系统中还扮演者其他角色,那么任何使用到该单例类的地方都会初始化类的实例变量,而不管是否会用到。
     *
     */
    @ThreadSafe
    public class HungrySingleton {
        /**
         * 私有化的构造函数,确保单例不会再系统中的其他地方被实例化
         */
        private HungrySingleton() {
            System.out.println("HungrySingleton is created");
        }
    
        /**
         * private保证无法直接被访问,static保证只有一份
         */
        private static HungrySingleton instance = new HungrySingleton();
    
        /**
         * static允许通过类直接获取,public表明这是一个公共的允许访问的入口
         * @return
         */
        public static HungrySingleton getInstance() {
            return instance;
        }
    
        /**
         * 创建字符串的工具方法,模拟该单例扮演的其他角色
         * @return
         */
        public static String  createString(){
            System.out.println("createString in HungrySingleton");
            return "demo string";
        }
    }
    

    DoubleCheckedLockingSingleton

    /**
     * double-checked locking( 双重检查加锁 )
     * 这种方式主要用到两个关键字 volatile 和 synchronized
     */
    @ThreadSafe
    public class DoubleCheckedLockingSingleton {
    
        private volatile static DoubleCheckedLockingSingleton instance;
    
        private DoubleCheckedLockingSingleton() {
        }
    
        public static DoubleCheckedLockingSingleton getInstance() {
            // 第一次判空检查,volatile保证了可见性,多个线程看到的是一致的结果
            // 如果判断为非空,多个线程拿到同一个对象也是OK的,所以第一次判断时无需加锁
            if (instance == null) {
                synchronized (DoubleCheckedLockingSingleton.class) {
                    // 由于需要新建对象,因此需要加锁实现同步
                    if (instance == null) {
                        instance = new DoubleCheckedLockingSingleton();
                    }
                }
            }
            return instance;
        }
    }
    

    StaticClassSingleton

    /**
     * 懒汉式的单例模式,采用内部类实现,可以实现延迟加载
     *
     * 实例的建立是在类加载时完成,故天生对多线程友好,无需使用synchronized关键字进行同步。
     * 即实现了延迟加载,也避免了同步锁的性能开销,正所谓一举两得。
     */
    @ThreadSafe
    public class StaticClassSingleton {
        /**
         * 私有化的构造函数,确保单例不会再系统中的其他地方被实例化
         */
        private StaticClassSingleton() {
            System.out.println("LazySingleton is creating");
        }
    
        /**
         * 使用内部类来维护单例的实例,当StaticSingleton被加载时,其内部类并不会被初始化,故可以确保党
         * StaticSingleton被JVM载入时,不会初始化instance。
         */
        private static class SingletonHolder {
            private static StaticClassSingleton instance = new StaticClassSingleton();
        }
    
        /**
         * 当getInstance()方法被调用时,才会加载SingletonHolder,从而初始化instance。
         * @return
         */
        public static StaticClassSingleton getInstance() {
            return SingletonHolder.instance;
        }
    }
    

    EnumSingleton

    /**
     * 使用枚举实现的单例模式
     */
    @ThreadSafe
    public class EnumSingleton{
        // 私有的构造方法
        private EnumSingleton(){}
    
        // 内部枚举类
        private static enum Singleton{
            INSTANCE;
    
            private EnumSingleton singleton;
    
            //JVM会保证此方法绝对只调用一次
            private Singleton(){
                singleton = new EnumSingleton();
            }
            public EnumSingleton getInstance(){
                return singleton;
            }
        }
    
        // 公开的全局访问点
        public static EnumSingleton getInstance(){
            return Singleton.INSTANCE.getInstance();
        }
    
    }
    

    单元测试

    /**
     * 单例模式的测试类
     */
    public class SingletonTest extends BaseTest {
    
        private static final Integer LIMIT = 1000;
    
        @Test
        public void hungrySingletonTest() {
            long beginTime = System.currentTimeMillis();
            for (int i = 0; i < LIMIT; ++i) {
                HungrySingleton.getInstance();
            }
            System.out.println("hungrySingleton:" + (System.currentTimeMillis() - beginTime));
        }
    
        @Test
        public void lazySingletonTest() {
            long beginTime = System.currentTimeMillis();
            for (int i = 0; i < LIMIT; ++i) {
                LazySingleton.getInstance();
            }
            System.out.println("lazySingleton:" + (System.currentTimeMillis() - beginTime));
        }
    
        @Test
        public void enumSingletonTest() {
            EnumSingleton obj1 = EnumSingleton.getInstance();
            EnumSingleton obj2 = EnumSingleton.getInstance();
            //输出结果:obj1==obj2?true
            System.out.println("obj1==obj2?" + (obj1 == obj2));
        }
    }
    

    参考资料

    1. 代码仓库-单例模式

    相关文章

      网友评论

        本文标题:单例模式

        本文链接:https://www.haomeiwen.com/subject/umzrdhtx.html