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

做者 : 夏至 欢迎转载,也请保留这段申明
blog.csdn.net/u011418943/…java

上一篇讲到策略模式,变更的代码须要用到策略模式,感兴趣的小伙伴能够看看.
传送门:Android 经常使用设计模式之 -- 策略模式android

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

一、饿汉式

就是初始化的时候,直接初始化,典型的以时间换空间的作法。设计模式

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

}复制代码

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

二、懒汉式

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

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

懒汉式如上所示,
优势:多线程

咱们只须要在用到的时候,才申请内存,且能够从外部获取参数再实例化,这点是懒汉式的最大优势了并发

缺点: 优化

单线程只实例了一次,若是是多线程了,那么它会被屡次实例this

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

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

2.一、如何实现懒汉式线程安全?

有人会说,简单啊,你既然是线程并发不安全,那么加上一个 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步来完成的。

  • 一、为 mSingleMode 建立内存
  • 二、new SingleMode() 调用这个构造方法
  • 三、mSingleMode 指向内存区域

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

  • 一、为 mSingleMode 建立内存
  • 三、mSingleMode 指向内存区域
  • 二、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了。。。。。。

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

三、静态式

什么叫静态式呢?回顾一下上面的饿汉式,咱们再刚开始的就初始化了,无论你需不须要,而咱们也说过,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);复制代码

四、枚举单例

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() 有失必有得吧。

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

相关文章
相关标签/搜索