Android设计模式-单例模式

概述

定义:确保某个类只有一个实例,而且自行实例化提供给外部使用。

使用场景:某个类型的对象只应该有且只有一个,或者避免创建多个对象消耗过多的资源时。如:访问IO或数据库时要考虑单例模式。

N种实现方式及比较

饿汉式


public class SingleTon {
    //将构造函数私有化
    private SingleTon() {
    }
 
    //创建私有实例对象
    private static final SingleTon singleTonInstance = new SingleTon();
 
    //对外提供方法,返回实例对象
    public static SingleTon getInstance() {
        return singleTonInstance;
    }
}

优点:饿汉式的好处是线程安全,因为虚拟机保证只会装载一次,再装载类的时候,是不会并发的,这样就保证了线程安全的问题。 
缺点:实例对象是static的,在声明的时候就实例化了,浪费资源。

懒汉式

public class SingleTon {
    //声明私有化
    private static SingleTon singleTonInstance;
    //将构造函数私有化
    private SingleTon() {
    }
    //懒汉式
    private static synchronized SingleTon getInstance(){
        if (null==singleTonInstance){
            singleTonInstance = new SingleTon();
        }
        return singleTonInstance;
    }
}

优点:用到的时候才会去实例化,在一定程度上节约了资源。

缺点:getInstance方法是用synchronized修饰的,该方法是同步的,为了保证线程安全,但是导致每次调用该方法的时候都会被同步,这样会消耗不必要的资源(不必要的同步开销)。所以这种模式一般不建议使用。

Double Check Lock(DCL模式):双重检查锁定

public class SingleTon {
    //声明私有化
    private static SingleTon singleTonInstance;
    //将构造函数私有化
    private SingleTon() {
    }
    //Double Check Lock
    public static SingleTon getInstance(){
        if (singleTonInstance==null){
            synchronized (SingleTon.class){
                if (singleTonInstance==null){
                    singleTonInstance = new SingleTon();
                }
            }
        }
        return singleTonInstance;
    }
}

可以看到getInstance()方法对singleTonInstance进行两次判空,对懒汉式进行了优化,只有在第一次实例化的时候才会走第二个分支,才会同步,避免了每次都同步造成的不必要的资源消耗。

优点:第一次执行getInstance方法时才会实例化,资源利用率高,效率高。

缺点:偶尔失效(高并发条件下,由于JDK版本问题,在jdk1.5之前会失败)。

静态内部类实现

public class SingleTon {
    //将构造函数私有化
    private SingleTon() {
    }
 
    public static SingleTon getInstance() {
        return SingleTonHoulder.singleTonInstance;
    }
 
    //静态内部类
    public static class SingleTonHoulder {
        private static final SingleTon singleTonInstance = new SingleTon();
    }
}

第一次调用getInstance()方法的时候,虚拟机才会加载SingleTonHoulder静态内部类

优点:线程安全,保证单例的唯一性,延迟了对象的实例化,是推荐的方式。

缺点:传参不方便。

那么这个时候,问题来了,参数怎么传递?这个确实没懒汉式方便,不过没关系,我们可以定义一个init()就可以了,只不过初始化的时候多了一行代码;

public class SingleTon {
    //将构造函数私有化
    private SingleTon() {
    }
 
    public static SingleTon getInstance() {
        return SingleTonHoulder.singleTonInstance;
    }
 
    //静态内部类
    public static class SingleTonHoulder {
        private static final SingleTon singleTonInstance = new SingleTon();
    }

    private Context mContext;
    public void init(Context context){
        this.mContext = context;
    }
}

初始化:

SingleTon mSingleTon = SingleTon.getInstance();
mSingleTon.init(this);

枚举

以上的单例实现方法都没有考虑一个因素:反序列化,即使构造函数是私有的,反序列化仍然有特殊的途径去创建类的一个新的实例。但是同构枚举实现单例不会有这样的问题,因为枚举提供了序列化机制。

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

使用容器实现单例

public class SingleTonManager {
    private static Map<String, Object> objMap = new HashMap<>();
 
    private SingleTonManager() {
    }
 
    public static void registerService(String key, Object object) {
        if (!objMap.containsKey(key)) {
            objMap.put(key, object);
        }
    }
 
    public static Object getService(String key) {
        return objMap.get(key);
    }
}

将多种单例类型注入到一个统一的管理类中,使用时根据key获取对应的对象,这种模式使得我们可以和管理多种类型的单例,并且在使用的时候可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏的具体的实现,降低了耦合度。

Android源码中单例使用

LayoutInflater.from(Context context);

EventBus.getDefault()

ImageLoader.getInstance();

……

他们实现单例的方式不同,大家可以去看源码

总结

单例模式是使用频率较高的设计模式,但是由于客户端通常没有高并发的情款,选择哪种实现方式并不会有太大影响。但是出于效率考虑,推荐使用“DCL”和“静态内部类”实现方式。

单例模式的优点:

1.在内存中只有一个实例,减少内存开支

2.只生产一个实例,减少系统性能的性能开销

3.避免对资源的多重占用。

4.可以在系统设置全局的访问点,优化和共享资源访问。(例如可以设置一个单例类,负责所有数据表的映射处理)

单例的缺点;

1.单例一般没有接口,扩展很困难。

2.单例如果持有Context对象,很容易引起内存泄漏,最好传递全局的Application Context。

    原文作者:元嘉草草丶
    原文地址: https://blog.csdn.net/weiyj2015/article/details/82462850
    本文转自网络文章,转载此文章仅为分享知识,如有侵权,请联系博主进行删除。
点赞