26
2017
09

读书笔记-设计模式-单例模式

本文资料来源《Android源码设计模式解析与实战》

  • 定义
  • 优缺点
  • 关键点
  • 饿汉单例模式
  • 懒汉模式
  • DCL双重检查锁定
  • 静态内部类单例模式
  • 枚举单例
  • 使用容器实现单例

单例模式的定义

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。
使用场景:访问IO和数据库等资源。

优缺点

优点

  1. 由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁的创建,销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。
  2. 由于单例模式只生成一个实例,所以,减少了系统的性能开销,当一个对象的产生需要比较多的资源时,如读取配置,产生其他依赖对象时,则可以通过在应用启动时直接产生一个单例对象,然后用永久驻留内存的方式解决。
  3. 单例模式可以避免对资源的多重占用,例如一个写文件操作,由于只有一个实例存在内存中,避免对同一个资源文件的同时写操作。
  4. 单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如,可以设计一个单例类,负责所有数据表的映射处理。

缺点

  1. 单例模式一般没有接口,扩展很困难,若要扩展,除了修改代码基本上没有第二种途径可以实现。
  2. 单例对象如果持有Context,那么很容易引发内存泄漏,此时需要注意传递给单例对象的Context最好是Application Context。

实现单例模式的几个关键点

  1. 构造函数必须Private
  2. 通过一个静态方法或者枚举返回单例类对象
  3. 确保单例类的对象有且只有一个,尤其是多线程环境下
  4. 确保单例类对象在反序列化时不会重新构建对象

饿汉单例模式

  • 缺点:即使不使用也实例化了,消化资源

示例代码:

public class JimWrong {

    private static final JimWrong mJim = new JimWrong ();

    private JimWrong (){
    }

    public static JimWrong getJim (){
        return mJim ;
    }
}

懒汉模式

  • 缺点:第一次加载,需要实例化,反应慢,最大问题是每次都要同步,消耗性能

示例代码

public class JimWrong{

    private static JimWrong mJim;
    private Singleton(){  }

    public static synchronized JimWrong getJim(){
        if (mJim==null){
            mJim= new JimWrong();
        }
        return mJim;
    }
}

DCL双重检查锁定

  • 使用最多的写法
  • 缺点 :双重检查锁定失效
    失效原因在于, mJim= new JimWrong();这句代码分成3部分:
    (1)给JimWrong的实例分配内存
    (2)调用JimWrong()的构造函数,初始化成员字段
    (3)将mJim对象指向分配的内存空间(此时mJim就不是null了)
    问题来了,由于Java内存模型中的Cache,寄存器到主内存会写顺序的规定,2,3顺序是无法保证。有可能顺序是1,3,2,这样的话,当线程a走完了第3步,线程b进来,直接返回mJim,而第2步还没走,就会导致错误。

代码示例

public class JimWrong{
    private static JimWrong mJim = null;
    private JimWrong() {
    }
    public static JimWrong getJim() {
        if (mJim == null) {
            synchronized (JimWrong.class) {
                if (mJim == null) {
                    mJim = new JimWrong();
                }
            }
        }
        return mJim ;
    }
}

静态内部类单例模式

-最佳写法 ,可以解决双重检查锁定失效

代码示例

public class JimWrong {
    private JimWrong (){
    }
    public static JimWrong getJim(){
        return JimWrongHolder.mJim;
    }
    private static class JimWrongHolder{
        private static final JimWrong mJim= new JimWrong ();
    }

}

枚举单例

  • 默认枚举实例的创建是线程安全的,并且在任何情况下它都是一个单例。
  • 反序列化的情况下,也是单例。这是其他形式所不能比的。
  • 通过序列化可以将一个单例的实例对象写到磁盘,然后再读回去,从而有效的获得一个实例。即使构造函数是私有的,反序列化时依然可以通过特殊的途径去创建类的一个新的实例,相当于调用该类的构造函数。反序列化操作提供了一个很特别的钩子函数,类中具有一个私有的,被实例化的方法readResolve(),这个方法可以让开发人员控制对象的反序列化。例如上述示例中,如果要杜绝单例对象在被反序列化时重新生成对象,那么必须加入如下方法:
private Object readResolve() throws ObjectStreamException{ return mJim; }

示例代码

public enum  JimWrongEnum {
    JIMWRONG;
    public void test(){}

}

使用容器实现单例模式

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

示例代码

public class SingletonManager{
private sttic Map<String,Object> objMap = new HashMap<String,Object>();
private Singleton(){}
public static void registerService(String key,object instance){
        if(!objMap.containsKey(key)){
            objMap.put(key,instance);   
        }
    }

public static Object getService(String key){
        return objMap.get(key);
    }
}
上一篇:Axure8.0汉化包+注册码 下一篇:拿到view真实宽高的4种方式