美文网首页
单例设计模式

单例设计模式

作者: Aeroball | 来源:发表于2019-12-24 19:23 被阅读0次

    什么是单例设计模式?

     单例模式,是一种常见的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式的方法创建的类在当前进程中只有一个实例。

     在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。

    单例模式有以下特点:

    1. 单例类只能有一个实例
    2. 单例类必须自己创建自己的唯一实例
    3. 单例类必须给所有的其他对象提供这一实例

    具体实现

    需要:

    1. 将构造方法私有化,使其不能在类的外部通过new关键字实例化该类对象
    2. 在该类内部产生一个唯一的实例化对象,并且将其封装为private static类型
    3. 定义一个静态方法返回这个唯一对象
    实现一:立即加载/“饿汉模式”
    /**
     * 饿汉式:在类初始化的时候,已经创建自己的实例
     */
    public class Singleton {
        //1,私有化构造方法
        private Singleton(){}
    
        //2,创建自己的单例对象
        private final static Singleton singleton = new Singleton();
    
        //3,提供获取当前类对象的方法
        public static Singleton getInstance(){
            return singleton;
        }
    }
    

    优点: 线程安全,实现简单
    缺点: 类一加载就初始化了对象,就算没有使用也一直占用着内存,会让内存开销变大

    实现二:延迟加载/“饿汉模式”
    /**
     *
     * 懒汉式:方法被调用的时候才实例化
     */
    public class Singleton {
    
        //1,私有化构造方法
        private Singleton(){}
    
        //2,静态私有的属性
        private static Singleton singleton = null;
    
        //3,提供获取当前类对象的方法
        public static Singleton getInstance(){
            if(singleton==null){
                singleton = new Singleton();
            }
            return singleton;
        }
    }
    

    优点: 实现简单,当getInstance方法第一次被调用时菜初始化instance变量,并分配内存,因此在某些特定条件下节约了内存。
    缺点: 线程不安安全

    实现三:线程安全的“懒汉式”
    public class Singleton {
    
        // 构造方法私有化
        private Singleton() {}
    
        // 将自身实例化对象设置为一个属性,并用static修饰
        private static Singleton instance;
        
        // 静态方法返回该实例,加synchronized关键字实现同步
        public static synchronized Singleton getInstance() {
            if(instance == null) {
                instance = new Singleton();
            }
            return instance;
        }
    }
    

    优点: 在多线程情况下,保证了“懒汉模式”的线程安全
    缺点: synchronized方法通常效率低,而且无论instance对象创建与否,线程都进行排队等候,效率低下。

    实现四: DCL双检锁机制
    public class Singleton {
    
        // 将自身实例化对象设置为一个属性,并用static修饰
        private static Singleton instance;
        
        // 构造方法私有化
        private Singleton() {}
        
        // 静态方法返回该实例
        public static Singleton getInstance() {
            // 第一次检查instance是否被实例化出来,如果没有进入if块
            if(instance == null) {
                synchronized (Singleton.class) {
                    // 某个线程取得了类锁,实例化对象前第二次检查instance是否已经被实例化出来,如果没有,才最终实例出对象
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    

    优点: 内存占用率高,效率高,线程安全
    缺点: 存在指令重排问题

    实现四中指令重排问题:
        Singleton s = new Singleton();
        执行上述语句时实际做了三件事:
            1. 开辟内存空间
            2. 实例化对象
            3. 将内存地址返回
        但在编译执行的时候会有指令重排问题,,编译器优化时为了减少内存的开销提高性能,执行顺序会变成:
            1=>3=>2,即一开辟内存就将内存地址返回了,然后才实例化对象。
    
    所以在实现四的单例模式中,可能出现以下情况:
        如下图线程A拿到线程锁执行new语句时,开辟了内存空间并返回内存地址,
        但是并没有实例化对象,此时线程B执行了外层判断对象s不为空,
        就将其返回,但此时对象并没有实例化,就会出现问题。
    
    image.png
    实现五:加关键字volatile
    public class Singleton {
    
        // 保证了并发编程中的有序性
        private volatile static Singleton instance;
        
        private Singleton() {}
        
        public static Singleton getInstance() {
            // 第一次检查instance是否被实例化出来,如果没有进入if块
            if(instance == null) {
                synchronized (Singleton.class) {
                    // 某个线程取得了类锁,实例化对象前第二次检查instance是否已经被实例化出来,如果没有,才最终实例出对象
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }
    
    注意:
        需要加volatile关键字的原因是,在并发情况下,如果没有这个关键字,
        在第5行会出现问题。因为第五行代码“instance = new Singleton()”
        并不是原子性操作,在JVM中被分为如下三个阶段执行:
            1. 为instance分配内存
            2. 初始化instance
            3. 将instance变量指向分配的内存空间
            
                由于JVM可能存在重排序,可能会执行第3步然后再执行第2步。也就是说可能
            会出现instance变量还没有初始化完成,其他线程就已经判断了该变量不为null,
            结果返回了 一个没有初始化的半成品。而加上volatile关键字,可以保证instance
            变量的操作不会被JVM重排。
    

    相关文章

      网友评论

          本文标题:单例设计模式

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