设计模式 - 单例模式

前言

如题,单例模式:用来创建出一个独一无二的,只有一个实例的对象。

应用场景

在线程池(Thread Pool),缓存(Cache),偏好设置,注册表,日志等这些情况下,都需要单一对象;
Android中,可能最常见的就是自己定义的 Base Application 了吧;其他的还有,第三方分享的统一入口,Event Bus的总线,消息机制中的 Message Queue;
另外还有一些具体的场景,比如在订外卖的时候,选择主食、配菜、酒水、餐具、送货地址...等等一个操作流程中,就存在一个唯一的订单对象;
其实,很多地方都有用,只是平常没有注意。

单例 VS 全局变量

  • 实例的唯一性:
    • 全部变量,可以在任意位置被重新赋值,存在着不可预料的风险
    • 单例,在 getInstance() 中,无对象则创建,有则跳过,以此来保证唯一性
  • 创建位置不确定:
    • 全局变量,需要开发者团队协商,按照类别定义在不同的文件中
    • 单例,getInstance() 定义在该类的内部,引用位置明确
  • 初始化时机和内存占用:
    • 全局变量,声明时即初始化,内存全部分配
    • 单例,可以延迟初始化,此时内存占用的很少,

PS:延迟初始化也是Android启动速度优化的一个点

创建的几种方式

按照单例的特性,我们可以总结一下创建的步骤:
1)需要一个类,且只能在该类内部初始化,这样就保证了单例对象不会再其他地方被重新赋值,转化成代码就是,将构造器私有化,像这样 private Singleton() {...}
2)私有化的构造器,无法像 new Singleton() 这样直接调用,于是乎,就有了 public static Singleton getInstance() {...} 这段代码;
3)到这里为止,创建就完成了,接下来我们需要保证唯一性,所以就需要一个静态对象来保持引用 private static Singleton uniqueInstance
4)最简单的单例就完成了,基本代码如下:

public class Singleton {

    private static Singleton uniqueInstance = new Singleton();
    private Singleton() {
    }

    public static Singleton getInstance() {
        return uniqueInstance;
    }    
}

PS:当然以上代码真的是不忍直视(其实也并不正确),接下来我们一点一点优化它。

1,添加懒加载的懒汉式
public class Singleton {

    private static Singleton uniqueInstance;
    private Singleton() {
    }
    
    public static Singleton getInstance() {
        if (uniqueInstance == null) {
            uniqueInstance = new Singleton();
        }

        return uniqueInstance;
    } 
}

PS:这段代码简单明了,而且使用了懒加载模式,但是却存在致命的问题。当有多个线程并行调用 getInstance() 的时候,就会创建多个实例。也就是说在多线程下不能正常工作。

2,线程安全的懒汉式:添加 synchronized 关键字修饰 getInstance()
public static synchronized Singleton getInstance() {
    if (instance == null) {
        instance = new Singleton();
    }
    return instance;
}

PS:虽然做到了线程安全,并且解决了多实例的问题,但是它并不高效。因为在任何时候只能有一个线程调用 getInstance() 方法。但是同步操作只需要在第一次调用时才被需要,即第一次创建单例实例对象时。这就引出了双重检验锁。

3,双重检验锁

会有两次检查 instance == null,一次是在同步块外,一次是在同步块内。为什么在同步块内还要再检验一次?因为可能会有多个线程一起进入同步块外的 if,如果在同步块内不进行二次检验的话就会生成多个实例了。

public static Singleton getSingleton() {
    if (instance == null) {                         // Single Checked
        synchronized (Singleton.class) {
            if (instance == null) {                 // Double Checked
                instance = new Singleton();
            }
        }
    }
    return instance ;
}

PS: 将 instance 变量声明成 volatile,是可以更好的解决线程安全的问题,关于这点,暂且不表。

4,线程安全的单例最简单实现:饿汉式 static final field
public class Singleton{
    // 类加载时就初始化
    private static final Singleton instance = new Singleton();
    
    private Singleton(){}
    public static Singleton getInstance(){
        return instance;
    }
}

PS:这种写法,简单而粗暴,但是并不完美,原因有二。1)不是懒加载;2)无法设置参数。

5,静态内部类 static nested class
public class Singleton {  
    private static class SingletonHolder {  
        private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
        return SingletonHolder.INSTANCE; 
    }  
}

PS:这种写法仍然使用 JVM 本身机制保证了线程安全问题;由于 SingletonHolder 是私有的,除了 getInstance() 之外没有办法访问它,因此它是懒汉式的;同时读取实例的时候不会进行同步,没有性能缺陷;也不依赖 JDK 版本。

6,最简洁的单例:枚举
public enum EasySingleton{
    INSTANCE;
}

PS:没有比这更简单的了~~~

总结

一般来说,单例模式有五种写法:懒汉式、饿汉、双重检验锁、静态内部类、枚举。个人会倾向于使用静态内部类的方式,相对会万金油。

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

推荐阅读更多精彩内容