等瞰android_android 浮动窗 android5 android7 - CSDN
  •  不管是Android开发还是Java开发,相信单例模式都是用的比较多的,平时再用的时候有没有想过,到底有多少种写法,或者有么有什么坑没有踩呢?带着这些问题我们先来了解一下什么情况下会用到单例模式。  一般在...

      年底了,手上的活不是很多,就想着将平时记录的笔记总结一下。准备总结一下平时常常使用的设计模式。本篇就是比较常用的单例(Singleton)模式。
      不管是Android开发还是Java开发,相信单例模式都是用的比较多的,平时再用的时候有没有想过,到底有多少种写法,或者有么有什么坑没有踩呢?带着这些问题我们先来了解一下什么情况下会用到单例模式。
      一般在希望系统中特定类只存在一个实例时,就可以使用单例模式。也就是说使用单例模式,最关心的是对象创建的次数,以及对象创建的时机。它的UML图也是非常的简单:

              SouthEast

      结构很简单,但是我们再使用时,还是想要有一些要求的:
      1.在调用getInstance()方法时返回一个且唯一的Singleton对象。
      2.能够在多线程使用时也能保证获取的Singleton对象唯一
      3.getInstance()方法的性能要保证
      4.能在需要的时候才初始化,否则不用初始化
      
      现在就来按照上边的要求来实现吧。
      
      写法一 饿汉式
      

    /**
     * 饿汉式
     * 基于ClassLoader的机制,在同一classLoader下,该方式可以解决多线程同步的问题,
     * 但是该种单例模式没有办法实现懒加载
     */
    public class SingletonHungry {
        /**
         * 在ClassLoader加载该类时,就会初始化mInstance
         */
        private static SingletonHungry mInstance = new SingletonHungry();
    
        private SingletonHungry() {
        }
    
        public static SingletonHungry getInstance() {
            return mInstance;
        }
     }

      以上就是饿汉式的写法,满足了上边说的第1,2条要求。该模式有几点要注意:
      1.默认构造方法需要私有化,不然外部可以随时的构造方法,这样就没法保证单例了。
      2.SingletonHungry 类型的静态变量mInstance也是私有化的。这样外部就不能直接获取到mInstance,并且正是由于mInstance是静态变量并且声明时就初始化了,我们知道根据java虚拟机和ClassLoader的特性,一个类在一个ClassLoader中只会被加载一次。并且这里的mInstance在加载时就已经初始化了,这可以确定对象的唯一性。也就是说保证了在多线程并发情况下获取到的对象是唯一的。
      当然该种方式肯定也是有缺点的,就是不能满足上边要求中的第三点,例如某类实例需求依赖在运行时的参数来生成,那么由于饿汉式在类加载时就已经初始化了,所以无法满足懒加载。那我们就来看看懒加载的写法。

      * 写法二 懒加载(非线程安全)*
      

    /**
     * 懒汉式
     * 只有在getInstance()时才会初始化mInstance
     * Created by chuck on 17/1/18.
     */
    public class SingletonLazy {
        private static SingletonLazy mInstance;
    
        private SingletonLazy() {
    
        }
    
        public static SingletonLazy getInstanceUnLocked() {
            if (mInstance == null) {//line1
                mInstance = new SingletonLazy();//line2
            }
            return mInstance;
        }
    }

      可以看出确实是在调用getInstanceUnLocked()方法时,才会初始化实例,实现了懒加载。但是在能否满足在多线程下正常工作呢?我们在这里先分析一下假设有两个线程ThreadA和ThreadB:
      ThreadA首先执行到line1,这时mInstance为null,ThreadA将接着执行new SingletonLazy();在这个过程中如果mInstance已经分配了内存地址,但是还没有完成初始化工作(问题就出在这儿,稍后分析),如果ThreadB执行了line1,因为mInstance已经指向了某一内存,所以将跳过new SingletonLazy()直接得到mInstance,但是此时mInstance还没有完成初始化,那么问题就出现了。造成这个问题的原因就是new SingletonLazy()这个操作不是原子操作。至少可以分解成以下上个原子操作:
      1.分配内存空间
      2.初始化对象
      3.将对象指向分配好的地址空间(执行完之后就不再是null了)
      
      其中第2,3步在一些编译器中为了优化单线程中的执行性能是可以重排的。重排之后就是这样的:
      1.分配内存空间
      3.将对象指向分配好的地址空间(执行完之后就不再是null了)
      2.初始化对象
      重排之后就有可能出现上边分析的情况:

       SouthEast

      那么既然这个方式不能保证线程安全,那我们之间加上同步不就可以了吗?这确实也是一种方法

      * 写法三 懒加载(线程安全)*
      

    /**
     * 懒汉式
     * 只有在getInstance()时才会初始化mInstance
     * Created by chuck on 17/1/18.
     */
    public class SingletonLazy {
        private static SingletonLazy mInstance;
    
        private SingletonLazy() {
    
        }
    
    /**
     * 方法名多了Locked表示是线程安全的,没有其他意义
     */
        public synchronized static  SingletonLazy getInstanceLocked() {
            if (mInstance == null) {
                mInstance = new SingletonLazy();
            }
            return mInstance;
        }
     }

      这里和线程不安全的懒加载方式就是多了一个synchronized关键字,保证了线程安全,但是这又带来了另外一个问题,性能问题。如果,有多个线程会频繁调用getInstanceLocked()方法的话,可能会造成很大的性能损失。当然如果没有多线程频繁调用的话,就不存在多少性能损失了。那么为了解决这个问题,有人提出了我们非常熟悉的双重检查锁定(简称DCL)。

      * 写法四 双重检查锁定(DCL)*
      

    /**
     * 双重检查锁定DCL
     * Created by chuck on 17/1/18.
     */
    public class SingletonLazy {
        private static SingletonLazy mInstance;
    
        private SingletonLazy() {
    
        }
    
        public static SingletonLazy getInstance() {
            if (mInstance == null) {//第一次检查
                synchronized (SingletonLazy.class) {//加锁
                    if (mInstance == null) {//第二次次检查
                        mInstance = new SingletonLazy();//new 一个对象
                    }
                }
            }
            return mInstance;
        }
     }

      在相当长的时间里,我以为这个完美的平衡了并发和性能的问题,但后来看多有文章介绍,这个方法也是有问题的,而这个问题和上边介绍过的重排问题一样。还是举ThreadA和ThreadB的例子:
      当Thread经过第一次检查对象为null时,会接着去加锁,然后去执行new SingletonLazy(),上边已经分析过了,改步骤存在重排现象,如果发生重排,即mInstance分配了内存地址,但是很没有完成初始化工作,而此时ThreadB,刚好执行第一次检查(没有加锁),mInstance已经分配了地址空间,不再为null,那么ThreadB会获取到没有完成初始化的mInstance,这就出现了问题。当然方法还是有的,那就是volatile关键字(用法自己查吧)。在JDK1.5之后使用volatile关键字,将禁止上文中的三步操作重排,既然不会重排,也就不会出现问题了。
      
      

    /**
     * 双重检查锁定DCL
     * Created by chuck on 17/1/18.
     */
    public class SingletonLazy {
        private volatile static SingletonLazy mInstance;
    
        private SingletonLazy() {
    
        }
    
        public static SingletonLazy getInstance() {
            if (mInstance == null) {//第一次检查
                synchronized (SingletonLazy.class) {//加锁
                    if (mInstance == null) {//第二次次检查
                        mInstance = new SingletonLazy();//new 一个对象
                    }
                }
            }
            return mInstance;
        }
     }

      问题是解决了,但是volatile要在JDK1.5以上版本(JDK1.5之前的可以参考http://www.ibm.com/developerworks/cn/java/j-dcl.html)才能起作用,其还会屏蔽jvm做的代码优化,这些有可能导致程序性能降低,并且目前为止DCL已经有一些复杂了。有没有更简单的方法呢?答案是有的

      * 写法五 静态内部类*
      

    /**
     * 静态内部类方式实际上是结合了饿汉式和懒汉式的优点的一种方式
     * Created by chuck on 17/1/18.
     */
    public class SingletonInner {
        private SingletonInner() {
        }
    
        /**
         * 在调用getInstance()方法时才会去初始化mInstance
         * 实现了懒加载
         *
         * @return
         */
        public static SingletonInner getInstance() {
            return SingletonInnerHolder.mInstance;
        }
    
        /**
         * 静态内部类
         * 因为一个ClassLoader下同一个类只会加载一次,保证了并发时不会得到不同的对象
         */
        public static class SingletonInnerHolder {
            public static SingletonInner mInstance = new SingletonInner();
        }
    }

      这是一个很聪明的方式,结合了结合了饿汉式和懒汉式的优点,并且也不影响性能。为什么这么说?因为我们在单例类SingletonInner类中,实现了一个static的内部类SingletonInnerHolder,该类中定义了一个static的SingletonInner类型的变量mInstance,并且会在classLoader第一次加载SingletonInnerHolder这个类时进行初始化。这样做的好处是在classLoader在加载单例类SingletonInner时不会初始化mInstance。只有在第一次调用SingletonInner的getInstance()方法时,classLoader才会去加载SingletonInnerHolder,并初始化mInstance,并且由于ClassLoader的机制,一个ClassLoader同一个类,只加载一次,那么不管多少线程,得到的也是同一个类,保证了并发下是该方式是可用的。其缺点也是有的,有些语言不支持这种语法。
      接下来在介绍一种很简单的方式:
      
      写法六 枚举
      

    /**
     * Created by chuck on 17/1/18.
     */
    public enum SingletonEnum {
        SINGLETON_ENUM;
    
        private SingletonEnum() {
        }
    
        }

      就是这么的简单,改方式不仅能避免多线程并发同步的问题,而且还天生支持序列化,可以防止在反序列化时创建新的对象。是一种比较推荐的方式,在java中需要在JDK1.5以上才支持enum。
      总结:单例模式还有其他的实现方法,熟悉Android的同学都知道,Handler机制中用到的ThreadLocal其实就使用了一种单例,就是在处理并发时,保证每一个线程都有一个单例实现。在上述介绍的各种方式中,没有哪一个是绝对最好的,需要结合各自的情况决定。例如一般不要求懒加载的话,可以使用写法一饿汉式,如果要求懒加载,如果明确需要懒加载的,再根据是否需要线程安全考虑选择写法二,三。如果单例类需要反序列化,那么可以使用写法六枚举。总之,需要结合自己的实际情况来看。最后,再来看看几个问题:
      第一 、多ClassLoder情况,如果是多个ClassLoder都加载了单例类,那么就会出现多个同名的对象,这违背了单例模式的原则。解决这个问题,就要保证只有一个ClassLoder加载单例类。
      第二、单例类序列化问题,只要保证反序列化时,得到同一个对象就可以了,通过重写readResolve()方法可以实现。
      

    public class Singleton implements java.io.Serializable {     
    ... 
    
       private Object readResolve() {     
                return mInstance;     
       }    
    }  

      本文练习的项目地址https://github.com/hgchenkai/DesignPattern 有时间都亲手敲一下。

    展开全文
  • ANDROID设计模式之单例模式 版权声明:本文为 stormzhang 原创文章,可以随意转载,但必须在明确位置注明出处!!! http://stormzhang.com/designpattern/2016/03/27/android-design-pattern-singleton/ ...

    ANDROID设计模式之单例模式

    版权声明:本文为 stormzhang 原创文章,可以随意转载,但必须在明确位置注明出处!!!

    http://stormzhang.com/designpattern/2016/03/27/android-design-pattern-singleton/

    经常有人问我说Android学习如何进阶?不管你怎么走,设计模式可谓是进阶必备,对设计模式的理解与运用对你之后的代码书写与架构设计有很多的帮助作用,那么从今天开始我就抽时间来给大家分享下设计模式系列。

    什么是设计模式?其实简单的理解就是前人留下来的一些经验总结而已,然后把这些经验起了个名字叫Design Pattern,翻译过来就是设计模式的意思,通过使用设计模式可以让我们的代码复用性更高,可维护性更高,让你的代码写的更优雅。设计模式理论上有23种,但是我只会针对Android平台上常用的一些设计模式做分享,今天就先来分享下最常用的单例模式。

    饿汉式

    public class Singleton{
    
        private static Singleton instance = new Singleton();
    
        private Singleton(){}
    
        public static Singleton newInstance(){
            return instance;
        }
    }

    饿汉式 是最简单的实现方式,这种实现方式适合那些在初始化时就要用到单例的情况,这种方式简单粗暴,如果单例对象初始化非常快,而且占用内存非常小的时候这种方式是比较合适的,可以直接在应用启动时加载并初始化。 但是,如果单例初始化的操作耗时比较长而应用对于启动速度又有要求,或者单例的占用内存比较大,再或者单例只是在某个特定场景的情况下才会被使用,而一般情况下是不会使用时,使用饿汉式的单例模式就是不合适的,这时候就需要用到懒汉式的方式去按需延迟加载单例。

    懒汉式

    public class Singleton{
        private static Singleton instance = null;
    
        private Singleton(){}
    
        public static Singleton newInstance(){
            if(null == instance){
                instance = new Singleton();
            }
            return instance;
        }
    }

    懒汉式饿汉式的最大区别就是将单例的初始化操作,延迟到需要的时候才进行,这样做在某些场合中有很大用处。比如某个单例用的次数不是很多,但是这个单例提供的功能又非常复杂,而且加载和初始化要消耗大量的资源,这个时候使用懒汉式就是非常不错的选择。

    多线程下的单例模式

    上面介绍了一些单例模式的基本应用方法,但是上面所说的那些使用方式都是有一个隐含的前提,那就是他们都是应用在单线程条件下,一旦换成了多线程就有出错的风险。

    如果在多线程的情况下,饿汉式不会出现问题,因为JVM只会加载一次单例类,但是懒汉式可能就会出现重复创建单例对象的问题。为什么会有这样的问题呢?因为懒汉式在创建单例时是 线程不安全的,多个线程可能会并发调用他的newInstance方法导致多个线程可能会创建多份相同的单例出来。

    那有没有办法,使懒汉式的单利模式也是线程安全的呢?答案肯定是有的,就是使用加同步锁的方式去实现。

    懒汉式同步锁

    public class Singleton {
     
        private static Singleton instance = null;
     
        private Singleton(){
        }
     
        public static Singleton getInstance() {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
     
            return instance;
        }
    }

    这种是最常见的解决同步问题的一种方式,使用同步锁synchronized (Singleton.class)防止多线程同时进入造成instance被多次实例化。举个在Android使用这种方式的例子:

    InputMethodManager示例

    public final class InputMethodManager {
        //内部全局唯一实例  
        static InputMethodManager sInstance;
       
        //对外api  
        public static InputMethodManager getInstance() {
            synchronized (InputMethodManager.class) {
                if (sInstance == null) {
                    IBinder b = ServiceManager.getService(Context.INPUT_METHOD_SERVICE);
                    IInputMethodManager service = IInputMethodManager.Stub.asInterface(b);
                    sInstance = new InputMethodManager(service, Looper.getMainLooper());
                }
                return sInstance;
            }
        }
    }  

    以上是Android源码中输入法类相关的单例使用方式。

    但其实还有一种更好的方式如下:

    双重校验锁

    public class Singleton {
     
        private static volatile Singleton instance = null;
     
        private Singleton(){
        }
     
        public static Singleton getInstance() {
            // if already inited, no need to get lock everytime
            if (instance == null) {
                synchronized (Singleton.class) {
                    if (instance == null) {
                        instance = new Singleton();
                    }
                }
            }
     
            return instance;
        }
    }

    可以看到上面在synchronized (Singleton.class)外又添加了一层if,这是为了在instance已经实例化后下次进入不必执行synchronized (Singleton.class)获取对象锁,从而提高性能。

    以上两种方式还是挺麻烦的,我们不禁要问,有没有更好的实现方式呢?答案是肯定的。 我们可以利用JVM的类加载机制去实现。在很多情况下JVM已经为我们提供了同步控制,比如:

    • 在static{}区块中初始化的数据

    • 访问final字段时

    • 等等

    因为在JVM进行类加载的时候他会保证数据是同步的,我们可以这样实现:

    采用内部类,在这个内部类里面去创建对象实例。这样的话,只要应用中不使用内部类 JVM 就不会去加载这个单例类,也就不会创建单例对象,从而实现懒汉式的延迟加载和线程安全。

    实现代码如下:

    静态内部类

    public class Singleton{
        //内部类,在装载该内部类时才会去创建单利对象
        private static class SingletonHolder{
            public static Singleton instance = new Singleton();
        }
    
        private Singleton(){}
    
        public static Singleton newInstance(){
            return SingletonHolder.instance;
        }
    
        public void doSomething(){
            //do something
        }
    }

    这样实现出来的单例类就是线程安全的,而且使用起来很简洁,麻麻再也不用担心我的单例不是单例了。

    然而这还不是最简单的方式,Effective Java中推荐了一种更简洁方便的使用方式,就是使用枚举。

    枚举类型单例模式

    public enum Singleton{
        //定义一个枚举的元素,它就是Singleton的一个实例
        instance;
    
        public void doSomething(){
            // do something ...
        }    
    }

    使用方法如下:

    public static void main(String[] args){
       Singleton singleton = Singleton.instance;
       singleton.doSomething();
    }

    默认枚举实例的创建是线程安全的.(创建枚举类的单例在JVM层面也是能保证线程安全的), 所以不需要担心线程安全的问题,所以理论上枚举类来实现单例模式是最简单的方式。

    总结

    一般单例模式包含了5种写法,分别是饿汉、懒汉、双重校验锁、静态内部类和枚举。相信看完之后你对单例模式有了充分的理解了,根据不同的场景选择最你最喜欢的一种单例模式吧!

    展开全文
  • 一.定义: 确保某一个类只有一个实例,而且自行实例并向整个系统提供这个实例 二....避免产生过多的对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如,创建一个对象需要消耗的资源过多,如访问数据库...

    一.定义:
    确保某一个类只有一个实例,而且自行实例并向整个系统提供这个实例

    二.使用场景:
    避免产生过多的对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如,创建一个对象需要消耗的资源过多,如访问数据库或者IO 资源。

    三.实现单例模式的关键点:
    1.私有化构造方法
    2.通过静态方法或枚举返回单例类对象
    3.确保单例类的对象有且只有一个,尤其在多线程下
    4.确保单例对象在反序列化时不会重新构建对象

    四.实现方式

    1.Double Check Lock(DCL)实现模式

    public class SingleInstance
    {
        private static volatile SingleInstance sInstance = null;
    
        private SingleInstance()
        {
        }
    
        public static SingleInstance getInstance()
        {
            if (sInstance == null)
            {
                synchronized (SingleInstance.class)
                {
                    if (sInstance == null)
                    {
                        sInstance = new SingleInstance();
                    }
                }
            }
            return sInstance;
        }
    }

    那么这里为啥判空两次呢?
    第一次判空是为了避免不必要的同步,第二次判空则是为了在null的情况下创建实例。
    分析:sInstance=new SingleInstance()语句并不是一个原子操作,代码会被编译成多条编译指令,这里大致做了三件事情
    (1)给SingleInstance的实例分配内存
    (2)调用SingleInstance的构造方法SingleInstance(),初始化成员字段
    (3)将sInstance的对象指向分配的内存空间(此时sInstance就不是null了)

    但是,由于java编译器允许处理器乱序执行,以及jdk1.5之前的JMM(Java Memory Model)的Cache,寄存器到主存回写顺序的规定,上面的(2)(3)的顺序是不确定的,也就是说执行顺序可能是1-3-2,1-2-3,如果是前者,(3)执行完(2)还未执行,切换到B线程,这时候sInstance已经是非空的了,B这时候直接取走sInstance,使用的时候就会出错,这就是DCL失效的原因。
    jdk1.5之后,sun公司意识到这个问题,调整了JVM,具体化了volatile关键字,可以保证sInstance每次都是从主存中取。

    2.静态内部类单例模式:

    public class SingleInstance{
        private SingleInstance(){}
        public static SingleInstance getInstance(){
            return SingleInstanceHolder.sInstance;
        }
        private static class SingleInstanceHolder{
            private static final SingleInstance sInstance=new SingleInstance();
        }
    }

    第一次加载SingleInstance 并不是初始化sInstance,只有在第一次调用getInstance方法才会导致sInstance的初始化,这种方式能确保线程安全,单例对象唯一性。

    3.枚举类型

    public enum SingleInstance{
        INSTANCE;
        public void doSomething(){`这里写代码片`
    
        }
    }

    线程安全,任何时候都是单一实例。
    在上述的几种实现方式中,在反序列化的情况下他们会出现重新创建对象。为什么?
    序列化可以将一个单例的实例对象写到磁盘,然后在读出来,从而有效的获得一个实例。即使构造方法是私有的,反序列化仍可以通过一个特殊途径创建一个新的实例,,相当于调用构造方法,反序列化提供了一个特别的钩子函数,可以控制对象的反序列化。在上述的示例中,如果杜绝对象在反序列化时重新生成对象,必须加入readResolve函数。枚举则不会出现这种问题

    public class SingleInstance implements Serializable
    {
        private static final long serialVersionUID = 0L;
    
        private static volatile SingleInstance sInstance = null;
    
        private SingleInstance()
        {
        }
    
        public static SingleInstance getInstance()
        {
            if (sInstance == null)
            {
                synchronized (SingleInstance.class)
                {
                    if (sInstance == null)
                    {
                        sInstance = new SingleInstance();
                    }
                }
            }
            return sInstance;
        }
    
        private Object readResolve() throws ObjectStreamException
        {
            return sInstance;
        }
    }

    三点注意:
    1.单例中如果需要Context,最好传入ApplicationContext,否则很容易发生内存泄漏。
    2.可序列化的字段类型不是Java内置模型,这个字段也要实现Serializable.
    3.如果你调整了可序列化类的内部结构,没有修改serialVersionUID,就会抛出异常,最好的方案是将serialVersionUID设置为0L,这样即使改 了类的内部结构,不会抛出异常,只是修改的字段为0或者null。

    四.使用容器类实现单例模式

    public class SingleInstanceManager
    {
        private static Map<String, Object> sObjectMap = new HashMap<>();
    
        private SingleInstanceManager()
        {
        }
    
        public static void registerService(String key, Object instance)
        {
            if (!sObjectMap.containsKey(key))
            {
                sObjectMap.put(key, instance);
            }
        }
    
        public static Object getService(String key)
        {
            return sObjectMap.get(key);
        }
    }
    展开全文
  • 前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使用的...

      前段时间公司一些同事在讨论单例模式(我是最渣的一个,都插不上嘴 T__T ),这个模式使用的频率很高,也可能是很多人最熟悉的设计模式,当然单例模式也算是最简单的设计模式之一吧,简单归简单,但是在实际使用的时候也会有一些坑。
      转载请注明出处:http://blog.csdn.net/self_study/article/details/50835410
      PS:对技术感兴趣的同鞋加群544645972一起交流。

    设计模式总目录

      java/android 设计模式学习笔记目录

    特点

      确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
      单例模式的使用很广泛,比如:线程池(threadpool)、缓存(cache)、对话框、处理偏好设置、和注册表(registry)的对象、日志对象,充当打印机、显卡等设备的驱动程序的对象等,这些类的对象只能有一个实例,如果制造出多个实例,就会导致很多问题的产生,程序的行为异常,资源使用过量,或者不一致的结果等,所以单例模式最主要的特点:

    1. 构造函数不对外开放,一般为private;
    2. 通过一个静态方法或者枚举返回单例类对象;
    3. 确保单例类的对象有且只有一个,尤其是在多线程的环境下;
    4. 确保单例类对象在反序列化时不会重新构建对象。
    通过将单例类构造函数私有化,使得客户端不能通过 new 的形式手动构造单例类的对象。单例类会暴露一个共有静态方法,客户端需要调用这个静态方法获取到单例类的唯一对象,在获取到这个单例对象的过程中需要确保线程安全,即在多线程环境下构造单例类的对象也是有且只有一个,这是单例模式较关键的一个地方。
    • 主要优点单例模式的主要优点如下:
    1. 单例模式提供了对唯一实例的受控访问。因为单例类封装了它的唯一实例,所以它可以严格控制客户怎样以及何时访问它。
    2. 由于在系统内存中只存在一个对象,因此可以节约系统资源,对于一些需要频繁创建和销毁的对象单例模式无疑可以提高系统的性能。
    3. 允许可变数目的实例。基于单例模式我们可以进行扩展,使用与单例控制相似的方法来获得指定个数的对象实例,既节省系统资源,又解决了单例对象共享过多有损性能的问题。
    主要缺点
    1. 由于单例模式中没有抽象层,因此单例类的扩展有很大的困难。
    2. 单例类的职责过重,在一定程度上违背了“单一职责原则”。因为单例类既充当了工厂角色,提供了工厂方法,同时又充当了产品角色,包含一些业务方法,将产品的创建和产品的本身的功能融合到一起。
    3. 现在很多面向对象语言(如Java、C#)的运行环境都提供了自动垃圾回收的技术,因此,如果实例化的共享对象长时间不被利用,系统会认为它是垃圾,会自动销毁并回收资源,下次利用时又将重新实例化,这将导致共享的单例对象状态的丢失。
    4. 单例对象如果持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好是 Application Context。

    UML类图

    这里写图片描述
      类图很简单,Singleton 类有一个 static 的 instance对象,类型为 Singleton ,构造函数为 private,提供一个 getInstance() 的静态函数,返回刚才的 instance 对象,在该函数中进行初始化操作。

    示例与源码

      单例模式的写法很多,总结一下:

    lazy initialization, thread-unsafety(懒汉法,线程不安全)

      延迟初始化,一般很多人称为懒汉法,写法一目了然,在需要使用的时候去调用getInstance()函数去获取Singleton的唯一静态对象,如果为空,就会去做一个额外的初始化操作。

    public class Singleton {
        private static Singleton instance = null;
        private Singleton(){}
        public static Singleton getInstance() {
            if(instance == null) 
                instance = new Singleton();
            return instance;
        }
    }

      需要注意的是这种写法在多线程操作中是不安全的,后果是可能会产生多个Singleton对象,比如两个线程同时执行getInstance()函数时,然后同时执行到 new 操作时,最后很有可能会创建两个不同的对象。

    lazy initialization, thread-safety, double-checked(懒汉法,线程安全)

      需要做到线程安全,就需要确保任意时刻只能有且仅有一个线程能够执行new Singleton对象的操作,所以可以在getInstance()函数上加上 synchronized 关键字,类似于:

    public static synchronized Singleton getInstance() {
            if(singleton == null) 
                instance = new Singleton();
            return instance;
        }

    但是套用《Head First》上的一句话,对于绝大部分不需要同步的情况来说,synchronized 会让函数执行效率糟糕一百倍以上(Since synchronizing a method could in some extreme cases decrease performance by a factor of 100 or higher),所以就有了double-checked(双重检测)的方法:

    public class Singleton {
        private volatile static Singleton instance = null;
        private Singleton(){}
        public static Singleton getInstance() {
            if (instance == null){
                synchronized (Singleton.class){
                    if (instance == null){
                        instance = new Singleton();
                    }
                }
            }
            return instance;
        }
    }

      我们假设两个线程A,B同时执行到了getInstance()这个方法,第一个if判断,两个线程同时为true,进入if语句,里面有个 synchronized 同步,所以之后有且仅有一个线程A会执行到 synchronized 语句内部,接着再次判断instance是否为空,为空就去new Singleton对象并且赋值给instance,A线程退出 synchronized 语句,交出同步锁,B线程进入 synchronized 语句内部,if判断instance是否为空,防止创建不同的instance对象,这也是第二个if判断的作用,B线程发现不为空,所以直接退出,所以最终A和B线程可以获取到同一个Singleton对象,之后的线程调用getInstance()函数,都会因为Instance不为空而直接返回,不会受到 synchronized 的性能影响。

    volatile关键字介绍

      double-checked方法用到了volatile关键字,volatile关键字的作用需要仔细介绍一下,在C/C++中,volatile关键字的作用和java中是不一样的,总结一下:

    1. C/C++中的volatile关键字作用
    • 可见性“可见性”指的是在一个线程中对该变量的修改会马上由工作内存(Work Memory)写回主内存(Main Memory),所以会马上反应在其它线程的读取操作中。顺便一提,工作内存和主内存可以近似理解为实际电脑中的高速缓存和主存,工作内存是线程独享的,主存是线程共享的。
    • 不可优化性“不可优化”特性,volatile告诉编译器,不要对我这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,一定会被执行。
    • 顺序性”顺序性”,能够保证Volatile变量间的顺序性,编译器不会进行乱序优化。Volatile变量与非Volatile变量的顺序,编译器不保证顺序,可能会进行乱序优化。同时,C/C++ Volatile关键词,并不能用于构建happens-before语义,因此在进行多线程程序设计时,要小心使用volatile,不要掉入volatile变量的使用陷阱之中。
    java中volatile关键字作用Java也支持volatile关键字,但它被用于其他不同的用途。当volatile用于一个作用域时,Java保证如下:
    • (适用于Java所有版本)读和写一个volatile变量有全局的排序。也就是说每个线程访问一个volatile作用域时会在继续执行之前读取它的当前值,而不是(可能)使用一个缓存的值。(但是并不保证经常读写volatile作用域时读和写的相对顺序,也就是说通常这并不是有用的线程构建)。
    • (适用于Java5及其之后的版本)volatile的读和写建立了一个happens-before关系,类似于申请和释放一个互斥锁[8]。
    使用volatile会比使用锁更快,但是在一些情况下它不能工作。volatile使用范围在Java5中得到了扩展,特别是双重检查锁定现在能够正确工作[9]。

    上面有一个细节,java 5版本之后volatile的读与写才建立了一个happens-before的关系,之前的版本会出现一个问题:Why is volatile used in this example of double checked locking,这个答案写的很清楚了,线程 A 在完全构造完 instance 对象之前就会给 instance 分配内存,线程B在看到 instance 已经分配了内存不为空就回去使用它,所以这就造成了B线程使用了部分初始化的 instance 对象,最后就会出问题了。Double-checked locking里面有一句话

    As of J2SE 5.0, this problem has been fixed. The volatile keyword now ensures that 
    multiple threads handle the singleton instance correctly. This new idiom is 
    described in [2] and [3].

    所以对于 android 来说,使用 volatile关键字是一点问题都没有的了。
      参考文章
      Volatile变量
      C/C++ Volatile关键词深度剖析
      Java中volatile的作用以及用法

    eager initialization thread-safety (饿汉法,线程安全)

      “饿汉法”就是在使用该变量之前就将该变量进行初始化,这当然也就是线程安全的了,写法也很简单:

    private static Singleton instance = new Singleton();
    private Singleton(){
        name = "eager initialization thread-safety  1";
    }
    
    public static Singleton getInstance(){
        return instance;
    }

    或者

    private static Singleton instance  = null;
    private Singleton(){
        name = "eager initialization thread-safety  2";
    }
    
    static {
        instance = new Singleton();
    }
    public Singleton getInstance(){
        return instance;
    }

    代码都很简单,一个是直接进行初始化,另一个是使用静态块进行初始化,目的都是一个:在该类进行加载的时候就会初始化该对象,而不管是否需要该对象。这么写的好处是编写简单,而且是线程安全的,但是这时候初始化instance显然没有达到lazy loading的效果。

    static inner class thread-safety (静态内部类,线程安全)

      由于在java中,静态内部类是在使用中初始化的,所以可以利用这个天生的延迟加载特性,去实现一个简单,延迟加载,线程安全的单例模式:

    private static class SingletonHolder{
        private static final Singleton instance = new Singleton();
    }
    private Singleton(){
        name = "static inner class thread-safety";
    }
    
    public static Singleton getInstance(){
        return SingletonHolder.instance;
    }

    定义一个 SingletonHolder 的静态内部类,在该类中定义一个外部类 Singleton 的静态对象,并且直接初始化,在外部类 Singleton 的 getInstance() 方法中直接返回该对象。由于静态内部类的使用是延迟加载机制,所以只有当线程调用到 getInstance() 方法时才会去加载 SingletonHolder 类,加载这个类的时候又会去初始化 instance 变量,所以这个就实现了延迟加载机制,同时也只会初始化这一次,所以也是线程安全的,写法也很简单。
      

    PS

      上面提到的所有实现方式都有两个共同的缺点:

    • 都需要额外的工作(Serializable、transient、readResolve())来实现序列化,否则每次反序列化一个序列化的对象实例时都会创建一个新的实例。
    • 可能会有人使用反射强行调用我们的私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。

    enum (枚举写法)

      JDK1.5 之后加入 enum 特性,可以使用 enum 来实现单例模式:

    enum SingleEnum{
        INSTANCE("enum singleton thread-safety");
    
        private String name;
    
        SingleEnum(String name){
            this.name = name;
        }
    
        public String getName(){
            return name;
        }
    }

    使用枚举除了线程安全和防止反射强行调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,Effective Java推荐尽可能地使用枚举来实现单例。但是很不幸的是 android 中并不推荐使用 enum ,主要是因为在 java 中枚举都是继承自 java.lang.Enum 类,首次调用时,这个类会调用初始化方法来准备每个枚举变量。每个枚举项都会被声明成一个静态变量,并被赋值。在实际使用时会有点问题,这是 google 的官方文档介绍:
    Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android
    这篇博客也专门计算了 enum 的大小:胡凯-The price of ENUMs,所以枚举写法的缺点也就很明显了。

    登记式

       登记式单例实际上维护了一组单例类的实例,将这些实例存放在一个Map(登记薄)中,对于已经登记过的实例,则从Map直接返回,对于没有登记的,则先登记,然后返回。

    //类似Spring里面的方法,将类名注册,下次从里面直接获取。  
    public class Singleton {  
        private static Map<String,Singleton> map = new HashMap<String,Singleton>();  
        static{  
            Singleton single = new Singleton();  
            map.put(single.getClass().getName(), single);  
        }  
        //保护的默认构造子  
        protected Singleton(){}  
        //静态工厂方法,返还此类惟一的实例  
        public static Singleton getInstance(String name) {  
            if(name == null) {  
                name = Singleton.class.getName();  
                System.out.println("name == null"+"--->name="+name);  
            }  
            if(map.get(name) == null) {  
                try {  
                    map.put(name, (Singleton) Class.forName(name).newInstance());  
                } catch (InstantiationException e) {  
                    e.printStackTrace();  
                } catch (IllegalAccessException e) {  
                    e.printStackTrace();  
                } catch (ClassNotFoundException e) {  
                    e.printStackTrace();  
                }  
            }  
            return map.get(name);  
        }  
        //一个示意性的商业方法  
        public String about() {      
            return "Hello, I am RegSingleton.";      
        }      
        public static void main(String[] args) {  
            Singleton single3 = Singleton.getInstance(null);  
            System.out.println(single3.about());  
        }  
    }  

    这种方式我极少见到,另外其实内部实现还是用的饿汉式单例,因为其中的static方法块,它的单例在类被装载的时候就被实例化了。

    总结

      综上所述,平时在 android 中使用 double-checked 或者 SingletonHolder 都是可以的,毕竟 android 早就不使用 JDK5 之前的版本了。由于 android 中的多进程机制,在不同进程中无法创建同一个 instance 变量,就像 Application 类会初始化两次一样,这点需要注意。
      但是不管采取何种方案,请时刻牢记单例的三大要点:

    • 线程安全;
    • 延迟加载;
    • 序列化与反序列化安全。
      单例模式同时也有缺点:
    • 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现;
    • 单例对象如果持有 Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的 Context 最好为 Application Context。

    创建型模式 Rules of thumb

      有些时候创建型模式是可以重叠使用的,有一些抽象工厂模式原型模式都可以使用的场景,这个时候使用任一设计模式都是合理的;在其他情况下,他们各自作为彼此的补充:抽象工厂模式可能会使用一些原型类来克隆并且返回产品对象。
      抽象工厂模式建造者模式原型模式都能使用单例模式来实现他们自己;抽象工厂模式经常也是通过工厂方法模式实现的,但是他们都能够使用原型模式来实现;
      通常情况下,设计模式刚开始会使用工厂方法模式(结构清晰,更容易定制化,子类的数量爆炸),如果设计者发现需要更多的灵活性时,就会慢慢地发展为抽象工厂模式原型模式或者建造者模式(结构更加复杂,使用灵活);
      原型模式并不一定需要继承,但是它确实需要一个初始化的操作,工厂方法模式一定需要继承,但是不一定需要初始化操作;
      使用装饰者模式或者组合模式的情况通常也可以使用原型模式来获得益处;
      单例模式中,只要将构造方法的访问权限设置为 private 型,就可以实现单例。但是原型模式的 clone 方法直接无视构造方法的权限来生成新的对象,所以,单例模式原型模式是冲突的,在使用时要特别注意。

    源码下载

      https://github.com/zhaozepeng/Design-Patterns/tree/master/SingletonPattern

    引用

    http://www.tekbroaden.com/singleton-java.html?hmsr=toutiao.io&utm_medium=toutiao.io&utm_source=toutiao.io
    http://hedengcheng.com/?p=725
    http://www.cnblogs.com/hxsyl/archive/2013/03/19/2969489.html
    http://www.blogjava.net/kenzhh/archive/2013/03/15/357824.html
    http://blog.csdn.net/jason0539/article/details/23297037
    https://sourcemaking.com/design_patterns/singleton
    http://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java
    http://stackoverflow.com/questions/15792186/singleton-pattern-with-combination-of-lazy-loading-and-thread-safety
    https://en.wikipedia.org/wiki/Singleton_pattern
    https://en.wikipedia.org/wiki/Double-checked_locking#Usage_in_Java
    https://zh.wikipedia.org/wiki/Volatile%E5%8F%98%E9%87%8F
    http://jeremymanson.blogspot.com/2008/11/what-volatile-means-in-java.html
    http://www.jianshu.com/p/d8bf5d08a147
    http://preshing.com/20130702/the-happens-before-relation/
    http://blog.csdn.net/imzoer/article/details/8620801

    展开全文
  • 而在android开发中,必要的了解一些设计模式又是非常有必要的。对于想系统的学习设计模式的同学,这里推荐2本书。一本是Head First系列的Head Hirst Design Pattern,英文好的可以看英文,可以多读几遍。另外一本是...
  • 单例模式与Android

    2014-06-08 00:41:11
    单例模式(Singleton)一、 什么是单例模式单例模式,简单点来说就是设计一个类,使其在任何时候,最多只有一个实例,并提供一个访问这个实例的全局访问点。二、 为什么要单例在程序中的很多地方,只有一个实例是...
  • Android单例模式

    2016-02-01 11:01:28
    看到一段单例模式的代码,觉得挺有意思,如下:public abstract class Singleton<T> { private T mInstance; protected abstract T create(); public final T get() { synchronized (this) { ...
  • 方法一:(java习惯,在android平台开发时这样是不行的,因为它违背了单线程模型) 刚刚开始接触android线程编程的时候,习惯好像java一样,试图用下面的代码解决问题  new Thread( new Runnable() { public ...
  • 原文地址:http://www.apkbus.com/android-51718-1-1.html ...1.Android---UI篇---WebView(网络视图) http://www.apkbus.com/android-14259-1-1.html 2.webview学习记录 http://www.apkbus.com/android-44567-1-1.ht
  • Android Binder机制(一) Binder的设计和框架 首页博客链接关于我留言板 这是关于Android中Binder机制的一系列纯技术贴。花了一个多礼拜的时间,才终于将其整理完毕。行文于此,以做记录;也是将自己所得与大家分享...
  • Kotlin 单例

    2017-06-01 16:00:39
    单例的实现方法,可以通过同伴对象,或者 lazy。
  • Android的世界里,我们可以通过WindowManager将一个视图添加到屏幕上。这篇文章将探究一下与WindowManager相关的源码。
  • 因公司项目需要,所以研究了串口开发,下面分享开始,本次开发是利用谷歌开源的android-serialport-api项目(http://code.google.com/p/android-serialport-api/) 一、确定ndk 是否安装 和路径是否正确 ...
  • Android DataBinding 初探

    2017-04-19 09:08:13
    现在大家在项目的实际开发中很多用到了MVVM的开发模式,而说到MVVM就不得不提DataBinding,DataBinding是谷歌在2015年I/O大会上介绍的一个数据绑定框架,很好的解决了数据的绑定问题,使我们的数据绑定更加的解耦,...
  • //md5加密算法,将要加密的字符串传进去,返回加密后的字符串public static String encode(String password) { try { MessageDigest digest = MessageDigest.getInstance("md5"); byte[] result = digest.dig
  • Android运行在各种各样的设备中,有小屏幕的手机,超大屏的平板甚至电视。针对屏幕尺寸的差距,很多情况下,都是先针对手机开发一套App,然后拷贝一份,修改布局以适应平板神马超级大屏的。难道无法做到一个App可以...
  • BUG出现场景:在二次弹出...android.view.WindowManager$BadTokenException: Unable to add window -- token android.os.BinderProxy@406ab4c8 is not valid; is your activity running? at android.view.ViewRoot.
  • android 组件GridView实例

    2018-06-06 23:15:54
    本例子是对GridView的练习。... android:layout_width="match_parent"  android:layout_height="match_parent"  android:orientation="vertical" >    andr
  • 什么是内存泄露?...Java使用有向图机制,通过GC自动检查内存中的对象(什么时候检查由虚拟机决定),如果GC发现一个或一组对象为不可到达状态,则将该对象从内存中回收。也就是说,一个对象不被任何引用所...
  • 迄今为止,我们已经学些了众多gradle构建的概念以及如何运行tasks。在这一章,我们将对这些概念有一个更深的理解,然后开始构建我们自己的tasks。一旦我们掌握了如何编写自定义tasks,那么我们就可以试着编写自己的...
1 2 3 4 5
收藏数 87
精华内容 34
关键字:

等瞰android