设计模式(1)- 单例模式

前言

单例模式是一种比较简单也比较常用的模式。这种模式涉及到一个单一的类,该类负责创建自己的实例,同时也确保只有单个实例被创建,并有接口对外提供这个实例。一般单例模式多用于含有多个比较耗费资源的全局变量的工具类中。比如在一个Android应用中,应该只有一个ImageLoader实例,这个ImageLoader中含有线程池、缓存系统、网络请求等,创建多个的话很耗资源,就很没必要。下面讲几种比较常见的单例模式:

1.懒汉式

public class SingleTon {
    private static SingleTon instance ;

    private SingleTon(){}

    public static sysnchronized SingleTon getInstance() {
        if (instance == null) {
            instance = new SingleTon() ;
        }
        return instance ;
    }
}

SingleTon 类中先定义了一个静态对象,并且在第一次调用 getInstance 时初始化并返回该对象,后面每一次调用都会做一次判断。
懒汉式和饿汉式很容易记反有没有,可能都是褒义词的“近义词”吧。之前在看《Thinking In Java》一书中有提到 惰性初始化(Lazy Loading) 一词,就是让对象在将要使用之前的位置初始化,避免了内存浪费。2个放一起记就好记了,在第一次使用该单例对象的时候初始化的就是懒汉式。
懒汉式是通过来 synchronized 来加锁保证线程安全的,但是锁会影响效率,而且绝大多数情况下是不需要同步的。

2.饿汉式

public class SingleTon {
    private static final SingleTon instance = new SingleTon() ;

    private SingleTon(){}

    public static sysnchronized SingleTon getInstance() {
        return instance ;
    }
}

这种方式基于 ClassLoader 避免了多线程同步的问题,没有加锁,执行效率会提高。但是instance 在类装载的时候就实例化,浪费内存,很容易产生垃圾对象。关于类装载看一下这篇文章:单例模式之类的加载

3.双重检查锁(DLC, Double Check Lock)

public class SingleTon {
    private static final SingleTon instance = null ;

    private SingleTon(){}

    public static sysnchronized SingleTon getInstance() {
        if(instance == null) {
            sysnchronized(SingleTon.class) {
                if(instance == null) {
                    instance = new SingleTon() ;
                }
            }
        }
        return instance ;
    }
}

我的 synchronized / Lock+Volatile 这篇文章有关于java中3种锁的介绍,感兴趣的请移步看一下。
关于这里为什么要用到2个 if(instance == null)来判断,是因为 instance = new SingleTon() 这一句不是一个原子操作,用伪代码来表示分为三步:

inst = allocat() ;   // 分配内存 
constructor(inst) ;  // 执行构造函数
instance = inst ;    // 赋值

Java编译器允许处理器乱序执行,也就是可能会有指令冲排序发生,也就是说上面的第2、3步执行的顺序是无法保证的。也就是说执行顺序可能是1-2-3也可能是1-3-2.如果是1-3-2的话,当线程A执行了1-3时,instance已经不为 null 了,但是还没有调用构造函数初始化,这时候切换到线程B,B就直接取走了instance,在使用的时候就会出错了。当然 volatile 关键字也能保证原子操作不乱序执行,我上面的文章中有说明。

4.登记式(静态内部类)

public class SingleTon {

    private SingleTon(){}

    public static sysnchronized SingleTon getInstance() {
        return SingleTonHolder.instance ;
    }

    private static class SingleTonHolder {
        private static final SingleTon instance = new SingleTon() ;
    }
}

这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用惰性初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。
这种方式同样利用了 classloder 机制来保证初始化 instance 时只有一个线程,它跟第 DCL 种方式不同的是:第 DCL 种方式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。想象一下,如果实例化 instance 很消耗资源,所以想让它延迟加载,另外一方面,又不希望在 Singleton 类加载时就实例化,因为不能确保 Singleton 类还可能在其他的地方被主动使用从而被加载,那么这个时候实例化 instance 显然是不合适的。这个时候,这种方式相比第 DCL 种方式就显得很合理。

5.枚举单例

public class SingleTon {
    INSTANCE ;

    public void whateverMethod(){ }
}

在上述的4中单例实现中,在反序列化的时候都可能会创建一个新的实例。当然也可以通过钩子函数 readResolve() 来阻止反序列化的时候重新生成对象。但是枚举最大的有点就是简单,自动支持序列化机制,而且能保证绝对的单例。但是枚举是在Java1.5之后才加入的新特性。

总结:

不管以哪种形式实现单例模式,核心原理都是 将构造函数私有化,并且通过静态方法来获取一个唯一的实例,在获取的过程中必须保证线程安全、防止反序列化重新生成实例。一般情况下,不建议使用懒汉方式,建议使用饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用登记式。如果涉及到反序列化创建对象时,可以使用枚举方式。

参考书籍:

《Android源码设计模式解析与实战》

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

推荐阅读更多精彩内容