后浪 来学习吧!设计模式【1】单例模式

定义

保证一个类仅有一个实例,并提供一个访问它的全局访问点。

主要解决什么

一个全局使用的类频繁地创建与销毁。

何时使用

当您想控制实例数目,节省系统资源的时候。

如何解决

判断系统是否已经有这个单例,如果有则返回,如果没有则创建。

关键代码

构造函数是私有的。

单例的写法——七种

饿汉式、懒汉式(线程不安全)、懒汉式(线程安全)、双重校验锁、登记式/静态内部类、枚举、使用容器实现。

饿汉式

类加载时就初始化。非懒加载;是多线程安全。

优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。

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

懒汉式(线程不安全)

第一次调用才初始化。懒加载;非多线程安全。
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。因为没有加锁 synchronized,所以严格意义上它并不算单例模式。
这种方式 lazy loading 很明显,不要求线程安全,在多线程不能正常工作。

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

懒汉式(线程安全)

第一次调用才初始化。懒加载;是多线程安全。

优点:第一次调用才初始化,避免内存浪费。
缺点:必须加锁 synchronized 才能保证单例,但加锁会影响效率。
每次调用的时候 getInstance 都进行同步,造成不必要的开销。这种模式一般不建议使用。

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

DLC双重校验锁

采用双锁机制,安全且在多线程情况下能保持高性能。懒加载;是多线程安全。

既能够在需要时初始化单例,又能保证线程安全,且单例对象初始化后调用 instance 不进行同步锁。

优点:资源利用率高,第一次执行 getInstance 时单例对象才会被实例化,效率高。
缺点:第一次加载时,反应稍慢,也由于 Java 内存模型的原因偶尔会失败。在高并发环境下也有一定的缺陷,虽然发生概率很小。

DCL 模式是使用最多的模式,它能够在需要时才被实例化,并且能够在绝大多数场景下保证单例对象的唯一性,除非你的代码在并发场景比较复杂或者低于 JDK 6 版本下使用,否则,这种方式一般能够满足需求。

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) { // 第一层判断主要是为了避免不必要的同步 
        synchronized (Singleton.class) {  
        if (singleton == null) {  // 第二层的判断则是为了在 null 的情况下创建实例
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

登记式/静态内部类

懒加载;是多线程安全。
这种方式同样利用了 classloader 机制来保证初始化 instance 时只有一个线程,它跟饿汉式不同的是:饿汉式只要 Singleton 类被装载了,那么 instance 就会被实例化(没有达到 lazy loading 效果),而这种方式是 Singleton 类被装载了,instance 不一定被初始化。因为 SingletonHolder 类没有被主动使用,只有通过显式调用 getInstance 方法时,才会显式装载 SingletonHolder 类,从而实例化 instance。

静态内部类也有着一个致命的缺点,就是传参的问题,由于是静态内部类的形式去创建单例的,故外部无法传递参数进去,例如 Context 这种参数,所以,我们创建单例时,可以在静态内部类与 DCL 模式里自己斟酌。

public class Singleton {  
    private static class SingletonHolder {  
    private static final Singleton INSTANCE = new Singleton();  
    }  
    private Singleton (){}  
    public static final Singleton getInstance() {  
    return SingletonHolder.INSTANCE;  
    }  
}

枚举

非懒加载;是多线程安全。
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。

优点:枚举本身是线程安全的,且能防止通过反射和反序列化创建实例。
缺点:对 JDK 版本有限制要求(JDK1.5 之后才加入 enum 特性),非懒加载。

public enum Singleton {  
    INSTANCE;  
    public void whateverMethod() {  
    }  
}

使用容器实现

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

public class SingletonManager { 

    // 定义一个容器    
  private static Map<String, Object> objMap = new HashMap<String,Object>();
  private Singleton() { 
  }
  public static void registerService(String key, Objectinstance) {
    if (!objMap.containsKey(key) ) {
      objMap.put(key, instance) ;
    }
  }
  public static ObjectgetService(String key) {
    return objMap.get(key) ;
  }
}

android中的应用

容器单例模式,以 Context.LAYOUT_INFLATER_SERVICE 举例。
从 setContentView 入口,全方位分析 LayoutInflater

Android中的系统级服务都是通过容器的单例模式实现方式,以单例形式存在,减少了资源消耗。
比如LayoutInflater Service,将这些服务以键值对的形式存储在一个HashMap容器中,用户使用时只需要根据key来获取到对应的ServiceFetcher,然后通过ServcieFetcher对象的getService函数来获取到具体的服务对象,第一次获取时会调用ServcieFetcher的createService函数创建服务对象,然后将该对象缓存到一个列表中,下次再取时直接从缓存中获取,避免重复创建对象,从而达到单例的效果。

总结

一般情况下,不建议使用懒汉方式,建议使用饿汉方式。只有在要明确实现 lazy loading 效果时,才会使用登记方式(静态内部类)。如果涉及到反序列化创建对象时,可以尝试使用枚举方式。如果有其他特殊的需求,可以考虑使用双检锁方式。

参考资料

设计模式 (一) 通过理论 + 代码示例 + Android 源码中单例模式来学习单例

单例模式

设计模式(二)单例模式的七种写法

感谢您阅读本文,希望能在微信公众号(见下方二维码)、掘金简书CSDN一起交流。

CoderWonder

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值