zoukankan      html  css  js  c++  java
  • 设计模式之单例

    设计模式之单例

    定义

    保证一个类仅有一个实例,并提供一个访问它的全局访问点。

    简单来说,也就是需要保证一个类在整个应用程序的生命周期内,只能存在一个实例(没有也行)。为了达成这个目标,应该要做到以下几点:

    1. 私有的构造器,如果是构造器被声明为public的,则无法控制其实例化;
    2. 一个获取实例的公开方法,也就是定义中所说的『提供一个访问它的全局访问点』;
    3. 一个用来保持此唯一实例的静态变量。

    实现方法

    首先,我们按照前面说的三点,写了以下代码:

    public class Singleton {
    
        private Singleton instance;
    
        private Singleton(){}
    
        public Singleton GetInstance(){
            return instance;
        }
    }
    
    

    这样,显然是有问题的。

    一是获取实例的公开方法getInstance,只是生命成public的,外部在没有Singleton的实例的情况下还是不能调用,就成了一个悖论了。所以需要把GetInstance方法声明为静态的。同样,由于需要被静态方法调用,同时还要用来保持唯一的实例,instance也需要声明为静态的。

    二就是还缺少了对instance变量的初始化,即对构造器的调用。我们既可以再声明instance是就进行初始化,也可以在静态代码段中对它进行初始化。

    于是乎,就有了下面两个版本的实现:

    方法一:饿汉一

    public class Singleton {
    
        private static Singleton instance = new Singleton();
    
        private Singleton(){}
    
        public static Singleton GetInstance(){
            return instance;
        }
    }
    
    

    方法二:饿汉二

    public class Singleton {
    
        private static Singleton instance;
        
        static {
            instance = new Singleton();
        }
    
        private Singleton(){}
    
        public static Singleton GetInstance(){
            return instance;
        }
    }
    
    

    由于这两种方法没有实质上的区别,都是在类被加载的时候就进行了实例的初始化(所以被称为饿汉式)。这也是饿汉式的下的两个特点:

    1. 线程安全,因为在类加载时已经完成来实例化;
    2. 性能低,实例化后的对象在应用的声明周期中未必就会被使用,所以可能会产生计算的浪费。

    方法三:懒汉

    由于饿汉式在类加载时就完成了实例化,导致了可能存在性能浪费,所以我们就考虑看看能不能在类被使用时才被实例化呢。如果听说过『懒加载』这个的词话,应该就会觉得这很easy了,在之前的基础上,很轻松就写出了下面代码(懒汉式的单例)。

    public class LazySingleton {
        private static LazySingleton instance;
        private LazySingleton() {}
    
        public static LazySingleton GetInstance() {
            if (instance == null) {
                instance = new LazySingleton();
            }
            return instance;
        }
    }
    

    乍一看,已经是可以了,至少在单线程下已经没问题了。但是在多线程的场景下呢,很容易就会产生A、B两个线程同时调用GetInstance方法,A线程先判断instance为null,准备进行实例化,但在实例化之前B线程也进行了instance为null的判断,最终结果是两个线程分别调用了一次私有构造器进行实例化,第二次实例化的结果会将第一次的覆盖掉。

    所以懒汉式有以下特点:

    1. 懒加载实现,第一次调用时实例化,不调用则不实例化,故计算效率高;
    2. 线程不安全,上面已经详细描述来是如何产生线程冲突的。

    方法四:懒汉(线程安全)

    为了解决上面方法的缺陷,也就是所谓的线程不安全,我们找到了synchronized这个关键字,也只加了这个关键字,得到下面的代码。

    public class LazySingleton {
        private static LazySingleton instance;
        private LazySingleton() {}
    
        public static synchronized LazySingleton GetInstance() {
            if (instance == null) {
                instance = new LazySingleton();
            }
            return instance;
        }
    }
    

    与前一种方法相比,在实现上只是在getInstance方法上增加了synchronized关键字,使得GetInstance方法同步,同一时间只能有一个线程执行这个方法,那我们之前说的线程不安全的问题显然就不在了,这样是不是就完美来呢?世界没那么美好,我们又引入了新的问题。

    由于是在整个GetInstance方法上加锁(同步),但是因为实际上只需要进行一次实例化(也只允许进行一次),所以绝大多数场景下是不需要同步的,所以在并发场景下会导致效率降低,相当于多车道在这里并成单车道了。

    方法五:懒汉(双重检查锁定)

    既然还有问题,那我们就来继续进行优化。上面的方法因为把整个GetInstance方法设置为synchronized,所以导致多线程在这里受阻,那我们把同步的范围缩小一点儿,看看情况会不会好一些。

    public class LazySingleton {
        private static volatile LazySingleton instance;
        private LazySingleton() {}
    
        public static LazySingleton getInstance() {
            if (instance == null) {
                synchronized(LazySingleton.class){
                    if (instance == null) {
                        instance = new LazySingleton();
                    }
                }
            }
            return instance;
        }
    }
    

    优化之后,把锁定范围进行了收缩,只在需要进行初始化实例时才进行同步,之后就不再进行同步。

    这样我们就得到了一种效率较高,并且线程安全的单例模式的构造方法。

    PS. 如果有仔细看代码,您或许会发现我们在声明instance变量的时候,用了一个volatile关键字,如果需要一些解释的话,可以参考Java中的volatile在使用双层检查实现单例模式的解读,后面我也可能来单独说一下这个。

    第六种:静态内部类

    还有一种使用静态内部类来实现的单例,也被各种推荐。因为内部静态类是要在有引用了以后才会装载到内存的,这样就同样实现了懒加载;同时,静态内部类的静态变量的初始化,也是在被加载时进行的初始化,天然的完成来对进程安全的控制。

    public class InnerStaticClassSingleton {
        private Singleton() {}
    
        private static class SingletonInstance {
            private static final Singleton INSTANCE = new Singleton();
        }
    
        public static InnerStaticClassSingleton getInstance() {
            return SingletonInstance.INSTANCE;
        }
    }
    

    To Be Continued

    http://bigman.pw
  • 相关阅读:
    Day-11 闭包和迭代器
    Day-01 Python基础
    Day-10 函数的进阶
    Day-09 初识函数
    Day-08 文件操作
    Day-07 基础数据类型补充 set集合 深浅拷贝
    Day-06 小数据池 再谈编码
    Day-05 基础数据类型字典dict
    Day-04 基础数据类型list, tuple
    NodeJs获取两个日期间的所有日期
  • 原文地址:https://www.cnblogs.com/renzhiwei/p/7735329.html
Copyright © 2011-2022 走看看