Android 常用设计模式(二) -- 单例模式(详解)

作者 : 夏至 欢迎转载,也请保留这段申明
http://blog.csdn.net/u011418943/article/details/60139644

上一篇讲到策略模式,变动的代码需要用到策略模式,感兴趣的小伙伴可以看看.
传送门:Android 常用设计模式之 -- 策略模式

单例模式的定义就不解释过多了,相信很多小伙伴在设计的时候,都用到这个模式;常用的场景为 数据库的访问,文件流的访问以及网络连接池的访问等等,在这些场景中,我们都希望实例只有一个,除了减少内存开销之外,也防止防止多进程修改文件错乱和数据库锁住的问题。
在这一篇文章中,我将带你分析 android 常见的集中单例模式,并详细分析他们的优缺点。让大家在以后的选择单例中,可以根据实际情况选择。
当然,如有错误,也欢迎指正。
下面是介绍:

1、饿汉式

就是初始化的时候,直接初始化,典型的以时间换空间的做法。

public class SingleMode{
    //构造方法私有化,这样外界就不能访问了
    private SingleMode(){
    };
    //当类被初始化的时候,就直接new出来
    private static SingleMode instance = new SingleMode();
    //提供一个方法,给他人调用
    public static SingleMode getInstance(){
        return instance;
    }
   
}

饿汉式的好处是线程安全,因为虚拟机保证只会装载一次,再装载类的时候,是不会并发的,这样就保证了线程安全的问题。
但缺点也很明显,一初始化就实例占内存了,但我裤子还没脱,不想用呢。

2、懒汉式

为了解决上面的问题,开了懒汉式,就是需要使用的时候,才去加载;

public class SingleMode{
    //构造方法私有化,这样外界就不能访问了
    private SingleMode(){
    };
    private static SingleMode mSingleMode;
    public static SingleModegetInstance(){ //这里就是延时加载的意思
        if (mSingleMode == null){
            mSingleMode = new SingleMode();
        }
        return  mSingleMode;
    }
}

懒汉式如上所示,
优点:

我们只需要在用到的时候,才申请内存,且可以从外部获取参数再实例化,这点是懒汉式的最大优点了

缺点:

单线程只实例了一次,如果是多线程了,那么它会被多次实例

至于问什么说它是线程不安全的呢?先下面这张图:

我们假设一下,有两个线程,A和B都要初始化这个实例;此时 A 比较快,已经判断 mSingleMode 为null,正在创建实例,而 B 这时候也再判断,但此时 A 还没有 new 完,所以 mSingleMode 还是为空的,所以B 也开始 new 出一个对象出来,这样就相当于创建了两个实例了,所以,上面这种设计并不能保证线程安全。

2.1、如何实现懒汉式线程安全?

有人会说,简单啊,你既然是线程并发不安全,那么加上一个 synchronized 线程锁不就完事了?但是这样以来,会降低整个访问速度,而且每次都要判断,这个真的是我们想要的吗?

由于上面的缺点,所以,我们可以对上面的懒汉式加个优化,如双重检查枷锁:

public class SingleMode{
    //构造方法私有化,这样外界就不能访问了
    private SingleMode(){
    };
    private static SingleMode mSingleMode;
    public static SingleMode getInstance(){
        if (mSingleMode == null){
           synchronized (SingleMode.class){
               if (mSingleMode == null){  //二次检测
                   mSingleMode = new SingleMode();
               }
           }
        }
        return  mSingleMode;
    }
}

在上面的基础上,用了二次检查,这样就保证了线程安全了,它会先判断是否为null,是才会去加载,而且用 synchronized 修饰,则又保证了线程安全。

但是如果上面我们没有用 volatile 修饰,它还是不安全的,有可能会出现null的问题。为什么?这是因为 java 在 new 一个对象的时候,它是无序的。而这个过程我们假设一下,假如有线程A,判断为null了,这个时候它就进入线程锁了 mSingleMode = new SingleMode();,它不是一蹴而就,而是需要3步来完成的。

  • 1、为 mSingleMode 创建内存
  • 2、new SingleMode() 调用这个构造方法
  • 3、mSingleMode 指向内存区域

那你可能会有疑问,这样不是很正常吗?怎么会有 null 的情况?
非也,java 虚拟机在执行上面这三步的时候,并不是按照这样的顺序来的,可能会打乱,这儿就是java重排序,比如2和3调换一下:

  • 1、为 mSingleMode 创建内存
  • 3、mSingleMode 指向内存区域
  • 2、new SingleMode() 调用这个构造方法

那这个时候,mSingleMode 已经指向内存区域了,那这个时候它就不为 null了,而实际上它并未获得构造方法,比如构造方面里面有些参数或者方法,但是你并未获取,然而这个时候线程B过来,而 mSingleMode已经指向内存区域不为空了,但方法和参数并未获得, 所以,这样你线程B在执行 mSingleMode 的某些方法时就会报错。

当然这种情况是非常少见的,不过还是暴露了这种问题所在。
所以我们用volatile 修饰,我们都知道 volatile 的一个重要属性是可见性,即被 volatile 修饰的对象,在不同线程中是可以实时更新的,也是说线程A修改了某个被volatile修饰的值,那么我线程B也知道它被修改了。但它还有另一个作用就是禁止java重排序的作用,这样我们就不用担心出现上面这种null 的情况了。如下:

public class SingleMode{
    //构造方法私有化,这样外界就不能访问了
    private SingleMode(){
    };
    private volatile static SingleMode mSingleMode;
    public static SingleMode getInstance(){
        if (mSingleMode == null){
           synchronized (SingleMode.class){
               if (mSingleMode == null){  //二次检测
                   mSingleMode = new SingleMode();
               }
           }
        }
        return  mSingleMode;
    }
}

看到这里,是不是感觉爬了几百盘的坑,终于上了黄金段位了。。。
然而,并不是,你打了排位之后发现还是被吊打,所以我们可能还忽略了什么。
没错,这种方式,依旧存在缺点:
由于volatile关键字会屏蔽会虚拟机中一些必要的代码优化,所以运行效率并不是很高。因此也建议,没有特别的需要,不要大量使用。

笔者就遇到,使用这种模式,不知道什么原因,第二次进入 activity的时候,view 刷不出来,然而数据对象什么的都存在,调得我心力交瘁,欲生欲死,最后换了其他单例模式就ok了,希望懂的大侠告诉我一下,我只能怀疑volatile了。。。。。。

那你都这样说了,那还怎么玩,有没有一种更好的方式呢?别急,往下看。

3、静态式

什么叫静态式呢?回顾一下上面的饿汉式,我们再刚开始的就初始化了,不管你需不需要,而我们也说过,Java 再装载类的时候,是不会并发的,那么,我们能不能zuo做到懒加载,即需要的时候再去初始化,又能保证线程安全呢?当然可以,如下:

public class SingleMode{
    //构造方法私有化,这样外界就不能访问了
    private SingleMode(){
    };
    public static class Holder{
        private static SingleMode mSingleMode = new SingleMode();
        public static SingleMode getInstance(){
            return  mSingleMode;
        }
    }
}

除了上面的饿汉式和懒汉式,,静态的好处在于能保证线程安全,不用去考虑太多、缺点就在于对参数的传递比较不好。
那么这个时候,问题来了,参数怎么传递?这个确实没懒汉式方便,不过没关系,我们可以定义一个init()就可以了,只不过初始化的时候多了一行代码;如:

public class SingleMode {
    //构造方法私有化,这样外界就不能访问了
    private SingleMode(){
    };
    public static class Holder{
        private static SingleMode mSingleMode = new SingleMode();
        public static SingleMode  getInstance(){
            return  mSingleMode;
        }
    }
    private Context mContext;
    public void init(Context context){
        this.mContext = context;
    }
}

初始化:

 SingleMode mSingleMode = SingleMode.Holder.getInstance();
 mSingleMode.init(this);

4、枚举单例

java 1.4 之前,我们习惯用静态内部类的方式来实现单例模式,但在1.5之后,在 《Effective java》也提到了这个观点,使用枚举的优点如下:

  • 线程安全
  • 延时加载
  • 序列化和反序列化安全

所以,现在一般用单个枚举的方式来实现单例,如上面,我们改一下:

public static SingleMode getInstance(){
        return Singleton.SINGLETON.getSingleTon();
    }
    public enum Singleton{
        SINGLETON ; //枚举本身序列化之后返回的实例,名字随便取
        private AppUninstallModel singleton;
        
        Singleton(){ //JVM保证只实例一次
            singleton = new AppUninstallModel();
        }
        // 公布对外方法
        public SingleMode getSingleTon(){
            return singleton;
        }
    }

好吧,这样就ok了,但还是那个问题,初始化参数跟静态类一样,还是得重新写个 init() 有失必有得吧。

这样,我们的单例模式就学完了。

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

推荐阅读更多精彩内容