• 单例设计模式


    单例设计模式是各种设计模式中最简单的,但是实际编码过程中使用最多的模式;面试中也经常被问到。我们来review一下单例设计模式

    饿汉模式

    public class Apple {
        private static Apple instance = new Apple();
    
        private Apple(){}
    
        public static Apple getInstance(){
            return instance;
        }
    }
    

    饿汉模式会在类加载的时候就初始化实例,而非使用时

    懒汉模式

    public class Apple {
        private static Apple instance = null;
    
        private Apple(){}
    
        public static Apple getInstance(){
            if(instance==null){
                instance = new Apple();
            }
            return instance;
        }
    }
    

    懒汉模式下,实例会在使用时再去初始化;但是这种懒汉模式有线程安全问题,多线程情况下可能被创建多个实例。

    线程安全的懒汉模式

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

    上面的带锁的懒汉模式解决了线程安全的问题,但是效率不高,每次只允许一个线程获取实例。

    双重校验的懒汉模式

    public class Apple {
        private static Apple instance = null;
    
        private Apple(){}
    
        public static Apple getInstance(){
            if(instance==null){			//第1次检查
                synchronized (Apple.class){
                    if(instance==null)	//第2次检查
                        instance = new Apple();
                }
            }
            return instance;
        }
    }
    

    第1次检查在synchronized外面,然后同步锁住代码块;第2次检查是为了防止在初始实例化的时候,线程B在同步块等待,线程A已经进入同步块并初始化了实例,等A退出同步块,线程B进入同步块不需要在初始实例。

    上述双重校验还是有问题,原因是new Apple()并不是原子操作,实际上是分成3个步骤。

    1. 给 instance 分配内存
    2. 调用 Singleton 的构造函数来初始化成员变量
    3. 将instance对象指向分配的内存空间(执行完这步 instance 就为非 null 了)

    由于这三个步骤可能按照1-3-2来执行,当3执行完了instance就非null了,这时线程B执行getInstance就会获得对象并使用就会报错。具体的解释参考左耳朵耗子的博文

    双重校验版本2

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

    使用volatile主要目的是禁止指令重排序,让new Apple()按照1-2-3的步骤执行。这样就能防止上述双重校验版本1的问题

    静态内部类方法

    public class Apple {

    private Apple(){}
    
    public static  Apple getInstance(){
        return AppleHolder.instance;
    }
    public static class AppleHolder{
        private static Apple instance = new Apple();
    }
    

    }
    静态内部类的方法,是JVM机制保证线程安全;当多个线程同时getInstance时,AppleHolder类的加载是JVM加载的,不会有线程问题;又是懒汉模式在需要时初始化。

    总结

    单例设计模式,有4中方式:饱汉式、懒汉式、双重校验方式、静态内部类方式。其中双重校验方式还有优化版本,在实际变成开发中推荐使用静态内部类的方式。

    博主原创,转载请标明出处!
    联系方式: 微信:corolla_zhaojd
    Email: zhaojiandongzju@gmail.com

  • 相关阅读:
    magento删除local.xml后必须要页面安装
    magento后台无法打开
    ubuntu安装phpmyadmin
    数据结构有关于最优二叉树——最优哈夫曼树的建立过程和哈夫曼编码
    计算后缀表达式的过程(C#)
    mysql语句的相关操作整理
    mysql在控制台里出现中文问号问题
    Wampserver由橙变绿的解决过程
    重装win7系统的过程
    数据结构(C#):图的最短路径问题、(Dijkstra算法)
  • 原文地址:https://www.cnblogs.com/oldtrafford/p/6820716.html
Copyright © 2020-2023  润新知