安卓设计模式の单例模式

来源:互联网 发布:p2p平台数据分析指标 编辑:程序博客网 时间:2024/05/01 23:43

单例模式

是一种常用的软件设计模式。在它的核心结构中只包含一个被称为单例的特殊类。通过单例模式可以保证系统中,应用该模式的类一个类只有一个实例。即一个类只有一个对象实例

在安卓中,常用到的单例模式有六种:
饿汉\懒汉\懒汉线程安全\DCL\静态内部类\枚举;下面将一一作介绍

一、饿汉模式

code

public class HungrySingleton {    private static final HungrySingleton mHungrySingleton = new HungrySingleton();    private HungrySingleton(){        System.out.println("Singleton is created");    }    public static HungrySingleton getInstance(){        return mHungrySingleton;    }    public static void createString(){        System.out.println("createString is Singleton");    }    public static void main(String[] args) {        HungrySingleton.createString();    }}

输出为

Singleton is createdcreateString is Singleton

该模式的缺点比较明显,就是无法对instance实例做延时加载,加载时会有负载产生,例如构造中的代码会被执行~
对此模式进行优化,就是以下介绍的懒汉模式。

二、懒汉模式

code

public class LazySingleton {    // 先不加载,等把类创建出来再利用getLazySingleton给instance赋值    private static LazySingleton instance;    private LazySingleton(){        System.out.println("Singleton is created");    }    public static LazySingleton getInstance(){        if (instance == null) {// 第一次调用时会被初始化            instance = new LazySingleton();        }        return instance;    }    public static void createString(){        System.out.println("createString is Singleton");    }    public static void main(String[] args) {        LazySingleton.createString();    }}

输出为:

createString is Singleton

该模式就能减少单例创建过程中产生的负载,并且可以延时加载,什么时候需要加载,什么时候就调用getInstance即可
但是,该方法也有个弊端,就是在并发创建单例对象的过程中,这种单例的特点会全部丢失..
看下下面的代码示例:

public class MyThread extends Thread{    @Override    public void run() {        System.out.println(LazySingleton.getInstance().hashCode());    }    public static void main(String[] args) {        MyThread[] mts = new MyThread[10];        for (int i = 0; i < mts.length; i++) {            mts[i] = new MyThread();        }        for (int j = 0; j < mts.length; j++) {            mts[j].start();        }    }}

输出为:

Singleton is created7896426Singleton is created746194978964267461949746194974619497461949746194974619497461949

创建了不止一个对象,这就是说,单例模式失效了,这就是其不足之处:在多线程并发下这样的实现是无法保证实例的唯一的,对此我们又有了改进方法,就是下面的懒汉线程安全模式~

三、懒汉线程安全模式

code

//方式一:方法中声明synchronized关键字    public static synchronized LazySafetySingleton getInstance(){        if (instance == null) { //懒汉            instance = new LazySafetySingleton();        }        return instance;    }    //方式二:同步代码块方式实现    public static LazySafetySingleton getInstance1(){        synchronized (LazySafetySingleton.class) {            if (instance == null) { //懒汉                instance = new LazySafetySingleton();            }        }        return instance;    }

虽然该模式相比懒汉模式是性能安全的,但是它有性能效率上的问题,如何解决此问题呢,没错就是DCL模式~

四、DCL(Double Check Lock双重检查锁)

code

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

这个getInstance方法中对mSingleton进行了两次判空:第一次是为了避免不必要的同步锁;第二层是为了在null的时候创建实例。
不过这种模式会出现DCL失效:
在多线程下,假设A线程执行到mSingleton=new Singleton()的时候,CPU并不是一次性执行完这条语句的,因为这不是一个原子操作(指不会被线程调度机制打断的操作),mSingleton=new Singleton()大致做了三件事:

  1. 给Singleton的实例分配内存
  2. 调用Singleton的构造方法
  3. 将mSingleton指向分配的内存空间(这个时候mSingleton才不为空)

由于Java编译器允许处理器乱序执行,所以上面的第二步第三步的执行顺序没法得到保证。执行顺序可能是1-2-3也可能是1-3-2。
当A线程执行顺序是1-3-2的时候,如果执行到了1-3,第2步还没执行的时候,如果B线程判断mSingleton==null的时候就会的发哦FALSE的结果,从而返回一个错误的单例。
当然,改进方法如上面代码所做的,将变量用volatile修饰,这样可以禁止JVM的指令重排序优化,从而达到顺序执行的目的。

五、静态内部类
public class StaticInnerSingleton {    private StaticInnerSingleton(){    }    public static StaticInnerSingleton getInstance(){        return SingletonHolder.sinstance;    }    private static class SingletonHolder{        private static final StaticInnerSingleton sinstance = new StaticInnerSingleton();     }}

推荐面试啊什么的都用这种单例,它具有三个优点:

  • 性能上的优越:不使用synchonized关键字,减少性能上的开销
  • 可以实现延时加载:具备懒汉模式的优点
  • 同时它也是性能(线程)安全的:它提供的static关键字(进行区块初始化数据,保证数据在内存中只读一份),final字段(定义后无法被修改),这是Java为我们提供的很实用的保证线程安全的同步控制。
六、枚举
 public enum EnumSingleton {    //定义一个枚举的元素,它就是EnumSingleton的一个实例    INSTANCE;    public void otherMethods() {        System.out.println("Something");    }    public static void main(String[] args) {        EnumSingleton instance = EnumSingleton.INSTANCE;        instance.otherMethods();    }}

简简单单的一点代码就实现了一个线程安全,lazy loading的单例,具体可以看看枚举的用法。

总结

大体概括一下,推荐用静态内部类或者枚举的方法实现单例模式,其优点显而易见,代码也不难理解,但也要知道其他几种单例模式的特点以及需要改善的地方,这样的话算是理解了单例的几种模式。接下来是其他设计模式的讲解。