美文网首页
关于 "单例" 的一些小事

关于 "单例" 的一些小事

作者: JackDaddy | 来源:发表于2020-07-04 15:25 被阅读0次

本篇文章要讲解的是关于单例模式的一些问题。无论是在面试或是在工作中我们都会使用到单例这种设计模式,实现单例的方式有很多,它们之间有什么区别和优劣呢?接下来就让本公子来一一替你解答吧。

什么是单例

单例模式指的是在整个系统 / 应用中只有 一个 实例对象。

单例有什么好处

如在系统中去请求网络时的网络对象往往只需要一个,可以反复利用,无需每次都新建,从而降低内存,提高性能等等。

单例的写法

在 Java 中存在着多种单例的写法,以下挑出几种常见的写法来介绍一下。在多种单例的写法中大都存在着以下几个特点:

  • 私有化构造方法
  • 提供一个公开静态的方法来获取单例对象实例
  • 注意在多线程的情况下是否线程安全
  • 注意在反序列化时是否会创建新的对象实例

接下来就来介绍一下单例的几种写法:

懒汉式

这是最简单的单例写法,先私有化构造方法,定义静态单例对象,提供公开静态获取单例对象的方法,在里面判断,如果对象为空时就新建一个对象。但这种写法是线程不安全的,在多线程情况下,如果一个线程在新建时另一个线程判断此时的单例为空,又去创建一个新的对象实例,这样就会造成创建多个对象实例的情况,因此是线程不安全的。

    /**
     * 懒汉式-线程不安全
     */
    static class Singlon_1 {
        //私有构造函数
        private Singlon_1() {

        }

        private static Singlon_1 mInstance_01;

        public static Singlon_1 getInstance() {
            if (mInstance_01 == null) {
                mInstance_01 = new Singlon_1();
            }
            return mInstance_01;
        }
    }
懒汉式(线程安全版)

上面说到懒汉式的写法是不安全的,因此在这个公开的静态方法加上 synchronized (加锁)关键字,从而保证线程安全,但是这种方式的加锁粒度太大,会耗费性能:

    /**
     * 懒汉式-线程安全-在方法体上+synchronized
     * 粒度太大-蚝性能
     */
    static class Singlon_2 {
        //私有构造函数
        private Singlon_2() {

        }

        private static Singlon_2 mInstance_02;

        public synchronized static Singlon_2 getInstance() {
            if (mInstance_02 == null) {
                mInstance_02 = new Singlon_2();
            }
            return mInstance_02;
        }
    }
懒汉式进阶->DCL 双加锁(线程安全)

这种方式降低锁的粒度,从而提高性能,同时由于 JDK 中指令执行顺序的不确定性,因此需要使用 volatile 关键字,这是从 JDK 1.5引入的一个关键字,主要有两个作用:

  • 防止指令重排
  • 每次获取数据强制从 "主存" 中拿,然后再存取到 "主存" 中
    同时前面说过还需要注意在反序列化时是否会创建新的单例对象,因此还要重写 readResolve 方法,返回自己定义的这个单例对象,防止在反序列化的时候创建新的单例对象:
    /**
     * DCL-双加锁
     * 懒汉式-线程安全
     */
    static class Singlon_3 {
        //私有构造函数
        private Singlon_3() {

        }

        //防止指令重排
        //每次获取数据从主存去拿
        private volatile static Singlon_3 mInstance_03;

        public static Singlon_3 getInstance() {
            if (mInstance_03 == null) {
                synchronized (Singlon_3.class) {
                    if (mInstance_03 == null) {
                        mInstance_03 = new Singlon_3();
                        //1.给构造函数分配地址
                        //2.实例化单例对象
                        //3.将构造函数赋值给单例对象
                    }
                }
            }
            return mInstance_03;
        }

        //重写此方法,防止反序列化时新建对象
        private Object readResolve() throws ObjectStreamException{
            return mInstance_03;
        }
    }
饿汉式(线程安全)

这种方式在创建实例的时候就直接赋值了单例对象,这种方式也是线程安全的:

    /**
     * 饿汉式-线程安全
     */
    static class Singlon_4 {
        //私有构造函数
        private Singlon_4() {

        }

        //一开始就实例化对象
        private static Singlon_4 mInstance_04 = new Singlon_4();

        public static Singlon_4 getInstance() {
            return mInstance_04;
        }
    }
静态内部类式(线程安全)

使用这种方式创建单例对象,有两个好处:

  • 可以做到需要使用时才加载,有效提高性能(有点懒加载的意思)
  • 同时保证了内存中只存在一份该对象实例
      /**
     * 静态内部类-线程安全
     */
    static class Singlon_5 {
        //私有构造函数
        private Singlon_5() {

        }

        private static class Holder {
            static Singlon_5 mInstance = new Singlon_5();
        }

        public static Singlon_5 getInstance() {
            //延迟加载-需要用到时才加载
            //同时保证内存中只存在一个对象实例
            return Holder.mInstance;
        }
    }
容器式

我们去看 Java 中的源码时,也可以看到有使用容器的方式去创建单例的方式:

    /**
     * 使用容器
     */
    static class Singlon_6 {

        private static Map<String, Object> objMap = new HashMap<>();

        public static void registInstance(String key,Object instance){
            if (!objMap.containsKey(key)){
                objMap.put(key,instance);
            }
        }

        public static Object getInstance(String key){
            return objMap.get(key);
        }
    }
枚举式(线程安全)

还可以使用枚举的方式去创建一个单例,在 Java 中使用枚举创建的单例对象默认是线程安全的:

    /**
     * 枚举-默认线程安全
     */
    enum Singlon_7{
        SINGLON_7
    }

以上就是 Java 中单例的几种写法,在 Kotlin 中应该如何创建单例呢?
我们对照Java中静态内部类式改造成 Kotlin 的写法吧:

// 只有一个实例
class NetManager {
    object Holder {
        val instance = NetManager()
    }

    // 看不到 static  可以 派生操作
    companion object {

        // 全部都是  相当于 Java static

        fun getInstance() : NetManager = Holder.instance
    }
}

其实在 Kotlin 中给我们提供了 object 关键字,只需使用了这个关键字,在里面写的内容就是一个单例,上面只是一个仿照Java静态内部类改造的写法而已,Kotlin 官方推荐我们使用的仍旧是使用 object 关键字来创建单例。

以上就是我对单例模式的一些小解析,希望对你有所帮助~

相关文章

  • 关于 "单例" 的一些小事

    本篇文章要讲解的是关于单例模式的一些问题。无论是在面试或是在工作中我们都会使用到单例这种设计模式,实现单例的方式有...

  • iOS 单例模式

    关于单例模式的详解,看完这几篇,就完全了然了。iOS 单例模式iOS中的单例模式iOS单例的写法

  • 单例模式安全之反射攻击

    单例模式安全之反射攻击 源码 单例模式这里就不谈了,什么是单例模式可参考七种Java单例模式详解,这里是关于单例模...

  • Kotlin项目中常见用法

    关于单例模式的使用,通过object声明的对象实际为单例模式对象 1不带参数单例 class MyClass pr...

  • java单例模式(推荐)

    单例模式有许多写法,可以结合自己理解选择一个自己喜欢的 引入一个其他人的博客关于单例的不错总结; 单例 单例模式 ...

  • Java 设计模式

    记住所有设计模式并不容易。这里有一些关于设计模式的故事,可能会有所帮助 创造器 Java设计模式:单例 单例模式是...

  • Kotlin学习笔记---单例模式(一)

    前言 关于单例模式,Java中最常见的设计模式,关于懒汉式还是饿汉式,各有优势,而Kotlin中也少不了单例模式,...

  • objc文章之避免滥用单例

    这是一篇objc上的文章,解了我很多的关于单例的困惑,原文地址 中文翻译地址 1.单例介绍 单例是Cocoa中被广...

  • [Swift开发者必备Tips] 单例 | log输出|la

    单例 Log 输出 lazy 修饰符和 lazy 方法 单例 单例是一个在 Cocoa 中很常用的模式了。对于一些...

  • 关于单例模式

    静态实现单例模式能较少的使用内存,也具备一定的安全性 饿汉模式实现单例模式的原理是要一次单例对象就创建一个单例对象...

网友评论

      本文标题:关于 "单例" 的一些小事

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