Android 设计模式--单例模式

一,定义 

单例模式就是确保某一个类只有一个实例,而且自行实例化,并向整个系统提供这个实例

二,使用场景

确保某个类只有一个对象的使用场景,避免产生多个对象消耗过多的资源,或者某种类型的对象只应该有且只有一个。例如,创建一个对象需要消耗过多的资源,如要访问IO和数据库等资源,就应该考虑单例模式了。

三,饿汉式单例

/**
 * 饿汉式单例模式
 * */
public class YuanZhen {

    private static final YuanZhen yuanzhen =new YuanZhen();

    private YuanZhen(){

    }

    public static YuanZhen getInstance(){
        return yuanzhen;
    }

}

饿汉式优点: 不需要加锁,性能优越

饿汉式缺点:一开始就加载好了对象,占用内存

四,懒汉式单例

/**
 * 懒汉式单例
 * */
public class YuanZhen1 {

    private  static YuanZhen1 yuanZhen1;

    private YuanZhen1(){

    }

    public static synchronized YuanZhen1 getInstance(){
        if(yuanZhen1 ==null){
            yuanZhen1 =new YuanZhen1();
        }
        return yuanZhen1;
    }
}

懒汉式单例就是在需要的时候去创建对象。

懒汉式单例的优点:单例只有在使用的时候才去加载,一定程度上节约了资源

懒汉式单例的缺点:

1.第一次加载时需要实例化,运行稍慢

2.每次调用synchronized同步锁,消耗资源

3,虽然加了同步锁,但是仍然可能会出现同步问题

五,DCL 双重锁检查机制实现单例模式

/**
 * DCL单例
 * */
public class YuanZhen2 {
    
    private static YuanZhen2 yuanZhen2;
    
    private YuanZhen2(){
        
    }
    public static  YuanZhen2 getInstance(){
        if(yuanZhen2 == null){
            synchronized (YuanZhen2.class){
                if(yuanZhen2 ==null){
                    yuanZhen2 =new YuanZhen2();
                }
            }
        }
        return yuanZhen2;
    }
}

在getInstance方法中,对yuanzhen2进行了2次判空,第一次判空是为了避免不必要的同步

第二次判空是为了在null的情况下创建实例

当线程A执行到new Yuanzhen2() 这句代码时,它最终会被编译成多条汇编指令:

1,给YuanZhen2的实例分配内存

2,调用YuanZhen2的构造函数,初始化成员变量

3,将yuanzhen2对象指向分配的内存空间

JDK1.5之前,它们在内存中的执行顺序可能1-2-3 也可能是1-3-2.

如果是1-3-2,那么如果执行完3之后,切换到了B线程,那这时yuanzhen2表面上看是非空,实际上是空的。那么后面再使用的时候就会报错,这就是DCL失效。

但是JDK1.5之后,调整了JVM.只需要将YuanZhen2的定义改成如下volatile方式,就能保证YuanZhen2对象每次都是从主内存中读取。

/**
 * DCL单例
 * */
public class YuanZhen2 {

    private static volatile YuanZhen2 yuanZhen2;

    private YuanZhen2(){

    }
    public static  YuanZhen2 getInstance(){
        if(yuanZhen2 == null){
            synchronized (YuanZhen2.class){
                if(yuanZhen2 ==null){
                    yuanZhen2 =new YuanZhen2();
                }
            }
        }
        return yuanZhen2;
    }
}

volatile会影响性能,但是如果可以避免DCL的失效问题,这点性能几乎可以忽略不计了

DCL的优点:

1,资源利用率高,第一次执行getInstance时单例对象才被初始化

2,避免了多余的同步问题

3,避免了线程的并发问题

DCL的缺点:

1,第一次加载时反省稍慢

2,使用synchronized 和 volaile 关键字,性能有一定消耗

六,静态内部类实现单例

/**
 * 静态内部类
 * */
public class YuanZhen3 {
    
    private YuanZhen3(){
        
    }
    
    public static YuanZhen3 getInstance(){
        return SingletonHolder.yuanzhen3;
    }
    
    private static class SingletonHolder{
        private static final YuanZhen3 yuanzhen3 =new YuanZhen3();
    }
}

当第一次加载YuanZhen3类时,并不会初始化yuanzhen3,只有在第一次调用Yuanzhen3的getInstance方法时才会导致yuanzhen3被初始化,因此,第一次调用getInstance方法会导致虚拟机加载SingletonHolder类,这种方式不仅能确保线程安全,也能够保证单例对象的唯一性,同时也延迟了单列的实例化,所以这是最推荐使用的方式。

七,枚举实现单例

/**
 * 枚举方式
 * */
public enum YuanZhen4 {
    INSTACE;
}

枚举方式是最简单的单例实现方式。而且它也是线程安全的,并且在任何情况下它都是一个单例。

在上述几种方式中,我们可以通过hook反序列化的函数readResolve来重新生成对象,除非我们重写readResolve函数将对象返回。但是枚举并不存在这个问题

八,使用容器实现单例

/**
 * 使用容器
 * */
public class YuanZhen5 {

    private static Map map =new HashMap<>();

    private YuanZhen5(){}

    public static void registerInstance(String key,Object instance){
        if(!map.containsKey(key)){
            map.put(key, instance);
        }
    }

    public static Object getInstance(String key){
        return map.get(key);
    }
}

使用:

YuanZhen5.registerInstance("1", YuanZhen1.getInstance());
YuanZhen5.registerInstance("2", YuanZhen2.getInstance());
YuanZhen2 yuanzhen2 = (YuanZhen2) YuanZhen5.getInstance("2");

在程序初始化时将多种单例类型注入到一个统一的管理类中,在使用时根据key获取对应类型的单例。

优点:

1,方便管理多种类型的单例

2,使用时可以通过统一的接口进行获取操作,降低了用户的使用成本

3,对用户隐藏了具体实现

4,降低了耦合度

九,总结

单例模式优点:

1,单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的创建,销毁时,单例模式的优势就非常明显了

2,由于单例模式只生成一个实例,所以,减少了系统的性能开销,当一个对象的创建需要比较多的资源时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式来解决。

缺点:

1,单例模式一般没有接口,扩展很难

2,单例对象如果持有context,很容易发生内存泄漏,此时需要注意,传递给单例对象的Context最好是Application Context。

你可能感兴趣的:(设计模式,单例模式)