《Android源码设计模式解析与实战》读书笔记-单例模式

介绍

单例对象的类必须保证只有一个实例存在。
某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

场景

避免产生多个对象消耗过多资源,或者某种类型的对象只应该有且只有一个。例如创建一个对象需要访问IO和数据库等资源,这时候就该考虑使用单例模式。

关键点

  1. 构造函数不对外开放,一般为private
  2. 通过一个静态方法或者枚举返回实例。
  3. 确保实例只有一个,尤其是在多线程的情况下。
  4. 确保单例对象在反序列化时不会重新构件对象。

简单示例

  • 懒汉模式
    懒汉模式声明一个静态对象,当用户第一次调用getInstance时进行初始化。具体实现如下:

      /**
     * 懒汉模式
     * Created by Bowen on 2016-04-20.
     */
    public class Singleton1 {
    
        private static Singleton1 mSingleton;
    
        private Singleton1(){
    
        }
    
        private synchronized static Singleton1 getInstance(){
            if(mSingleton == null){
                mSingleton = new Singleton1();
            }
    
            return mSingleton;
        }
    }
    
    

    需要注意的是,在getInstance方法上,我们使用了synchronized关键字修饰,这就是为了考虑到 关键点 中的第3点(确保实例只有一个,尤其是在多线程的情况下),考虑到如果有两个线程同时调用了getInstance方法,两个线程在进行mSingleton == null的判断时,结果都是true,导致mSingleton被实例化了两次,为了避免这种情况,我们需要用synchronized进行同步,但这又会导致另一个效率问题,每次调用getInstance方法时,都会进行同步,会造成不必要的同步开销。另外,懒汉模式在第一次调用getInstance时才进行实例化,反应稍慢。

  • 饿汉模式
    饿汉模式是在声明对象时就已经完成了初始化,具体实现如下:

    /**
     * 饿汉模式
     * Created by Bowen on 2016-04-21.
     */
    public class Singleton2 {
    
        private static Singleton2 mSingleton = new Singleton2();
    
        private Singleton2(){
    
        }
    
        public static Singleton2 getInstance(){
            return mSingleton;
        }
    }
    
    

    在饿汉模式中,并没有出现懒汉模式的问题:每次调用getInstance都需要同步。

  • Double CheckLock(DCL)模式
    DCL模式实现了需要时实例化,并保证了线程安全,同时单例对象初始化后以后不进行同步锁:

      /**
     * Double CheckLock(DCL)模式
     *
     * Created by Bowen on 2016-04-21.
     */
    public class Singleton3 {
    
        private volatile static Singleton3 mSingleton;
    
        private Singleton3(){
        }
    
        public static Singleton3 getInstance(){
            if (mSingleton == null){
                synchronized (Singleton3.class){
                    if (mSingleton == null){
                        mSingleton = new Singleton3();
                    }
                }
            }
            return mSingleton;
        }
    }
    
    

为了避免单例对象实例化以后进行不必要的同步,所以对实例对象进行第一次判空,如果此时实例对象已经被实例化,那么将不会执行同步代码块。当同步后依然为空,另外,用volatile关键字修饰实例对象,保证每一次都从主存中读取对象,避免多线程的情况下出现问题。

  • 静态内部类单例模式

    /**
     * 静态内部类单例模式
     *
     * Created by Bowen on 2016-04-21.
     */
    public class Singleton4 {
    
        private Singleton4(){
    
        }
    
        public static Singleton4 getInstance(){
            return SingletonHolder.mInstance;
        }
    
        private static class SingletonHolder{
            private static final Singleton4 mInstance = new Singleton4();
        }
    }
    
    

    当用户第一次调用getInstance方法时,mInstance才被初始化。静态内部类的单例模式,不仅保证了线程安全,也保证了对象的唯一性,并延迟了对象实例化的时间。

  • 枚举单例

      /**
     * 枚举单例
     *
     * Created by Bowen on 2016-04-21.
     */
    public enum Singleton5 {
        INSTANCE;
    
        public void doWork(){
    
        }
    }
    
    

    默认枚举示例的创建时线程安全的,并且反序列化的情况下仍然是一个单例。

Android源码中的单例模式

LayoutInflater类,我们经常会在ListView中的getView方法使用到LayoutInflater,示例代码:

@Override
public View getView(int position, View convertView, ViewGroup parent) {

    if (convertView == null){
        convertView = LayoutInflater.from(mContext).inflate(layoutiId,parent,false);
    }else {

    }

    return convertView;
}

通过LayoutInflater.from(context)来获取LayoutInflater的服务,下面是具体实现:

/**
 * Obtains the LayoutInflater from the given context.
 */
public static LayoutInflater from(Context context) {
    LayoutInflater LayoutInflater =
            (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    if (LayoutInflater == null) {
        throw new AssertionError("LayoutInflater not found.");
    }
    return LayoutInflater;
}

因为Context是一个Abstract类,getSystemService方法的具体实现在Context的实现类中,进入ContextImpl类定位到具体实现:

  public Object Object getService(ContextImpl ctx) {
    ArrayList<Object> cache = ctx.mServiceCache;
    Object service;
    synchronized (cache) {
    if (cache.size() == 0) {
    // Initialize the cache vector on first access.
    // At this point sNextPerContextServiceCacheIndex
    // is the number of potential services that are
    // cached per-Context.
    for (int i = 0; i < sNextPerContextServiceCacheIndex; i++) {
          cache.add(null);
        }
    } else {
    service = cache.get(mContextCacheIndex);
    if (service != null) {
        return service;
      }
    }
    service = createService(ctx);
    cache.set(mContextCacheIndex, service);
    return service;
  }
}

可以看到。ContextImpl中使用了一个HashMap存放何种Service,用户根据serviceName作为key查找对应的的服务,当第一次获取时,创建对象后并将对象存入map中,下次使用时只要从map中取出即可。

总结

  • 优点
    1. 单例模式在内中只有一个示例,减少内存开支。
    2. 减少系统的性能开销、当一个对象的产生需要较多资源时,如读取配置产生其他以来对象时,则可以通过应用启动时直接产生一个单例对象,然后用永久驻留内存的方式解决。
    3. 避免对资源的多重占用,例如对文件操作,由于只有一个示例存在内存中,避免对同一个资源文件同时写操作。
    4. 在系统设置全局的访问点,优化和共享资源访问。
  • 缺点
    1. 单例模式一般没有接口,扩展比较困难,如果要扩展,必须要修改代码。
    2. 如果单例对象持有Context,则很容易引起内存泄露(单例模式的生命周期和应用一样),所以最好传递Application的Context。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,271评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,275评论 2 380
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,151评论 0 336
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,550评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,553评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,559评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,924评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,580评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,826评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,578评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,661评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,363评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,940评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,926评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,156评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,872评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,391评论 2 342

推荐阅读更多精彩内容