美文网首页
单例模式

单例模式

作者: more2023 | 来源:发表于2020-04-30 10:45 被阅读0次

    一、什么是单例模式

    所谓单例就是确保程序中某一个类只有一个实例,并且自行实例化,同时向系统提供这个实例。
    单例模式的三个要点:
    1、一个类只有一个实例;
    2、必须自行创建这个实例;
    3、必须向系统提供这个实例;

    二、为什么使用单例模式

    在软件开发过程中,有些实例的创建非常消耗资源,初始化时间较长,如数据库连接池、一些工具类。秉承节约资源、提升性能的原则,创建出一个实例,提供全局使用。

    三、单例模式使用场景

    比如 OkHttp 工具类,初始化OkHttpClient实例,供全局使用。

    四、单例模式的结构

    7.png

    单例模式结构比较简单,一个类,一个私有静态实例,一个获取实例的公有方法。

    五、代码示例

    5.1、饿汉式

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

    饿汉式在类加载的时候就创建,不管用不用,先创建了再说,属于饥不择食。
    缺点:如果一直没被使用,便浪费了空间,属于典型的空间换时间。
    优点:每次调用的时候,不需要再判断,节省了运行时间,适合常用实例。

    5.2、懒汉式(非线程安全)

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

    懒汉式声明一个静态对象,在第一次调用时初始化。
    缺点:第一次调用才初始化,如果初始化过程较长,则程序反应稍慢。同时多线程环境下,因为线程非安全,可能会导致多个实例的情况。

    5.3、懒汉式(线程安全)

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

    懒汉式(线程安全)弥补了懒汉式(非线程安全),多线程环境下,线程不安全问题。
    缺点:懒汉式(线程安全)使用了 synchronized 会带来性能负担。
    优点:懒汉式使用时,才创建实例,节省资源;适用于单例用的不多,但单例比较复杂,加载和初始化需要消耗大量的资源场景。

    5.4、双重校验锁 (DCL)

    /**
     * 注意此处使用的关键字 volatile,它保证了可见性
     * 被volatile修饰的变量的值,将不会被本地线程缓存,
     * 所有对该变量的读写都是直接操作共享内存,从而确保多个线程能正确的处理该变量。
     */
    public class OkHttpUtil {
        private static volatile OkHttpUtil instance;
        private OkHttpUtil(){
        }
        public static OkHttpUtil getInstance() {
            if (instance == null){
                synchronized(OkHttpUtil.class){
                    if(instance == null){
                        instance = new OkHttpUtil();
                    }
                }
            }
            return instance;
        }
    }
    

    双重校验锁的方式,相较于 懒汉式(线程安全)方式 ,如果程序可以接受synchronized 带来的性能负担,则单例可以使用懒汉式(线程安全);如果对性能有更高的要求,则使用双重校验锁方式,在保证线程安全的前提下,又可以使性能不太受影响。
    双重校验锁的两次实例为空判断;第一次是为了不必要的同步;第二次是当 instance为空时,才创建。
    使用了同步锁,为什么还需要第二次为空判断??例如当线程A、线程B同时调用 getInstance()方法,并且同时执行第一次为空判断逻辑;如果是第一次实例未初始化,则获取到的 instance 都为null,继续执行下一步,线程A先获取到同步锁,并初始化实例,此时 instance不为空,线程A释放同步锁;线程B获取到同步锁,如果没有第二次 instance为空判断,则会再次对instance初始化,则造成多次实例化,就不再是单例了。
    缺点:代码相较于其他方式,代码较复杂,有一定的性能损耗。
    优点:相较于懒汉式(线程安全)方式,即保证了线程安全的前提下,又使性能不受很大影响。

    六、总结

    针对单例模式的应用,常用的方式是,饿汉式、懒汉式(线程安全)、双重校验锁;其他的静态内部类方式、枚举方式、使用容器方式就不再说明了。现将常用的方式,总结信息如下:
    1、饿汉式:对于使用频率很高的单例,推荐使用该方式;
    2、懒汉式(线程安全):对于使用频率不高,保证线程安全的前提下,并且可以接受 synchronized 带来的性能负担,则可以选择使用该方式。
    3、双重校验锁:即保证了线程安全的前提下,又使性能不受很大影响(常用推荐该方式)。

    相关文章

      网友评论

          本文标题:单例模式

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