Java与Kotlin的单例模式(霸气.jpg)

作者已经搬迁去隔壁网站,也欢迎大家关注我们的写作团队:天星技术团队

题外话

上一次被人说文章名字取得不霸气,于是这一次我采用了这么霸气的名字,但实际上我是一个很低调的人。设计模式刚入门的小伙伴可以先看看这篇《设计模式入门》,在文章末尾也将列出“设计模式系列”文章。欢迎大家关注留言投币丢香蕉。

什么是单例模式

  1. 单例模式是设计模式中最简单的形式之一。
  2. 一个类有且仅有一个对象实例,并自行实例化向整个系统提供。

为何要学习单例模式

1. 有些对象我们只需要一个。例如:线程池,缓存,对话框等等,创建太多此类实例可能会导致程序行为异常、资源使用过量等问题。
2. 防止造成资源浪费。静态变量也可以给整个系统提供一个实例,但此对象在程序一开始就被创建好了,万一在某次运行中没有使用到此对象,资源就被浪费了。
3. 没有富萝莉包养我。

走进单例模式

单例模式有三个要点,分别是:
1. 仅有一个实例。
2. 自动实例化。
3. 向整个系统提供。
这也就是我们在写单例模式时候必须要遵守的几点。根据上面的条件,我们能够得出:
1. 构造方法必须私有化,禁止外部随意new Singleton();
2. 类内部实例化一个实例对象。
3. 对外提供一个可以获取内部唯一实例对象的方法。

懒汉式

懒汉式这名字的精髓在于懒,在代码中,这种懒代表着需要的时候才创建实例,不需要就不创建了,这样保证了资源的不浪费。

Java懒汉式

public class JavaSingleton {

    private JavaSingleton(){}//私有构造方法
    private static JavaSingleton instance;//提供一个实例对象

     public static JavaSingleton getInstance(){//对外提供可获取唯一实例化对象的方法
        if(instance == null)
            instance = new JavaSingleton(); //延迟实例化
        return instance;
    }
}

Kotlin懒汉式1

class KotlinSingleton private constructor() {
    private var instance : KotlinSingleton? = null

    fun getInstance(): KotlinSingleton? {
        if(instance == null)
            instance = KotlinSingleton()
        return instance
    }
}

Kotlin懒汉式2

class KotlinSingleton private constructor() {
    companion object {
        val instance by lazy(LazyThreadSafetyMode.NONE) {
            KotlinSingleton()
        }
    }
}

// 在Kotlin 中调用
KotlinSingleton.instance.xx()
// 在Java 中调用
KotlinSingleton.Companion.getInstance().xx()

kotlin方式1就是直译了java方式,没啥说的。kotlin方式2写着很简洁,也很明了。要提一点的就是companion object,他类似public static,这一点从下面调用方式也看得出来。lazy属性表明是懒加载方式,LazyThreadSafetyMode.NONE表明了这种方式,线程不安全。
为啥不安全??!!
我们已经按照单例模式三个要点写出了单例模式,其实都已经讲完了。但为何还有其他一些写法呢? 因为我们是程序员啊!优化!优化!优化!
单线程进程下,懒汉式完全够用。但实际开发哪还有什么单线程程序。既然存在多线程,就存在并发性,如果两个线程同时进入非空判断,问题就出现了。线程一可能创建了一个instance,线程二因为已经非空判断为空了,所以也创建了个instance。这样程序肯定不会朝着你预想的方向去。所以我们可以给非空判断加一把锁,防止多线程同时进入非空判断。

同步锁

Java方式

public class JavaSingleton {

    private JavaSingleton(){}//私有构造方法
    private static JavaSingleton instance;//提供一个实例对象

     public static synchronized JavaSingleton getInstance(){//对外提供可获取唯一实例化对象的方法
        if(instance == null)
            instance = new JavaSingleton(); //延迟实例化
        return instance;
    }
}

Kotlin方式

class KotlinSingleton private constructor() {
    private var instance : KotlinSingleton? = null

    @Synchronized //用注解就okk了。
    fun getInstance(): KotlinSingleton? {
        if(instance == null)
            instance = KotlinSingleton()
        return instance
    }
}

简单!粗暴!
太粗暴了!Synchronized 是一把重型锁,对性能影响相当的大,像单例这样在软件中可能多次获取,那性能将会大大地降低!所以,这种方式不建议使用。

饿汉式

为了解决多线程的问题,又不想降低性能,“饿汉式”就来了。饿汉式名字精髓在于“饿”,迫切的想吃东西,在代码中表示,迫切的想有一个单例对象!即在初始化的时候,就把单例创建好,之后要用就直接return,但是不用就浪费了。这也是饿汉式的缺点。
Java方式

public class JavaSingleton {

    private JavaSingleton(){}//私有构造方法
    private static JavaSingleton instance = new JavaSingleton();//提供一个实例对象

    public static JavaSingleton getInstance(){//对外提供可获取唯一实例化对象的方法
        return instance;
    }
}

Kotlin方式

object KotlinSingleton {
    //null
}

// 在Kotlin 中调用
KotlinSingleton.xx()
// 在Java 中调用
KotlinSingleton.INSTANCE.xx()

酥糊!Kotlin!有牌面!

双重检查锁

前面讲的三种方式都有不同程度的缺点。而双重检查锁,既保证了延迟加载不浪费资源,又保证了较好的性能,不采用重型锁。但双重检查锁不适用于1.4以及更早版本的java。

Java方式

public class JavaSingleton {

    private JavaSingleton(){}
    //volatile确保了当instance初始化为JavaSingleton实例时,多个线程正确的处理instance变量。
    private volatile static JavaSingleton instance;

   public static JavaSingleton getInstance(){
       if(instance == null){
           synchronized (JavaSingleton.class){//只有第一次才彻底执行锁块代码
               if(instance == null){
                   instance = new JavaSingleton();
               }
           }
       }
       return instance;
   }
}

Kotlin方式1

class KotlinSingleton private constructor() {
    @Volatile  //用注解就okk了
    private var instance : KotlinSingleton? = null

    fun getInstance(): KotlinSingleton? {
        if(instance == null){
            synchronized(KotlinSingleton::class){
                if(instance == null){
                    instance = KotlinSingleton()
                }
            }
        }
        return instance
    }
}

Kotlin方式2

class KotlinSingleton private constructor() {
    companion object {
        val instance by lazy (LazyThreadSafetyMode.SYNCHRONIZED){
            KotlinSingleton()
        }
    }
}

// 在Kotlin 中调用
KotlinSingleton.instance.xx()
// 在Java 中调用
KotlinSingleton.Companion.getInstance().xx()

“双重检查锁”的没有明显的缺点,如果非要说一个,可能就是太复杂了。kotlin还好,java里面写着相当的复杂。如果程序对性能没有考虑的话,这样写显然就太麻烦了。

关于LazyThreadSafetyMode
延迟属性 Lazy 可选LazyThreadSafetyMode三种模式:

  • SYNCHRONIZED —— 双重检查锁式,默认使用。
  • PUBLICATION —— 允许多个线程同时初始化实例,但只采用最先返回的实例。
  • NONE —— 没有任何的线程安全的保证和开销。

内部类式(店长推荐!!)

不要私信问我店长是谁!
总之这种方式,解决了延迟加载,线程安全的问题,还代码量少!简直美滋滋!但跟之前不同的是,没有声明实例对象。
Java方式

public class JavaSingleton {

    private JavaSingleton(){}

    private static class Holder{
        private static JavaSingleton instance = new JavaSingleton();
    }

    public static JavaSingleton getInstance(){
        return Holder.instance;
    }
}

Kotlin方式

class KotlinSingleton private constructor() {
    companion object {
        fun getInstance() = Holder.instance
    }

    private object Holder{
        val instance = KotlinSingleton()
    }
}

在类加载时,因为没有调用getInstance()所以Holder也不会加载,这样就实现了懒加载。调用getInstance()时,JVM会主动保证类加载的安全性,所以线程也是安全的。kotlin的写法就是java的翻译版本。

民间大神版本

上面几种方式都是比较官方的版本,下面介绍几个民间版本,是真大神还是抖机灵,自行判断。我也去研究研究!


来自知乎@丌冰:
“我觉得这样就好了,什么懒加载,双重什么什么,什么什么的”

fun main(args: Array<String>) {
    Instance.INSTANCE.fun1()
    print(Instance.INSTANCE.fun2())
}
enum class Instance {
    INSTANCE;

    fun fun1(){
    }
    fun fun2():Any?{
        return null
    }
}

作者:丌冰
链接:https://www.zhihu.com/question/52377186/answer/303561470
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

可见Android官方并不推荐使用枚举,占用内存较多。

来自刘望舒设计模式(二)单例模式的七种写法

public class SingletonManager { 
  private static Map<String, Object> objMap = new HashMap<String,Object>();
  private Singleton() { 
  }
  public static void registerService(String key, Objectinstance) {
    if (!objMap.containsKey(key) ) {
      objMap.put(key, instance) ;
    }
  }
  public static ObjectgetService(String key) {
    return objMap.get(key) ;
  }
}

这也是比较少见的单例写法,将多个单例放在进SingletonManager 的静态map中统一管理。我是觉得有点太过于复杂,容易出错。

以上版本仅供参考,要是用了对程序造成了什么不好的影响,别找我。

关于单例的其他问题

关于继承: 不能继承!别想了。
关于单例: 尽量少使用单例。
关于我还没想到的问题: 欢迎加群讨论557247785。

总结

单例方式 优点 缺点
懒汉式 懒加载 线程不安全
同步锁 线程安全 多次获取的性能很低
饿汉式 简单、易写 可能引起资源浪费
双重检查锁 第一次获取才加锁 写法复杂
内部类式 简单、易写 暂无
民间大神式 欢迎留言跟我讨论 或者加qq群:557247785

以下是我“设计模式系列”文章,欢迎大家关注留言投币丢香蕉。
也可以进群跟大神们讨论。qq群:557247785

设计模式入门
Java与Kotlin的单例模式
Kotlin的装饰者模式与源码扩展
由浅到深了解工厂模式
为了学习Rxjava,年轻小伙竟作出这种事!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,658评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,482评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,213评论 0 350
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,395评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,487评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,523评论 1 293
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,525评论 3 414
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,300评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,753评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,048评论 2 330
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,223评论 1 343
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,905评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,541评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,168评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,417评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,094评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,088评论 2 352

推荐阅读更多精彩内容

  • 单例模式(SingletonPattern)一般被认为是最简单、最易理解的设计模式,也因为它的简洁易懂,是项目中最...
    成热了阅读 4,246评论 4 34
  • 在一个方法内部定义的变量都存储在栈中,当这个函数运行结束后,其对应的栈就会被回收,此时,在其方法体中定义的变量将不...
    Y了个J阅读 4,415评论 1 14
  • 1.单例模式概述 (1)引言 单例模式是应用最广的模式之一,也是23种设计模式中最基本的一个。本文旨在总结通过Ja...
    曹丰斌阅读 2,906评论 6 47
  • 这货书名够风骚,内容够罗嗦,逼格略低,这是多大仇啊。不过内容够详细,逻辑完整的让人抓狂,浅显易懂。还有尽是泛泛之谈...
    拉斯阅读 403评论 0 0