单例模式的七种写法

原文链接:https://blog.csdn.net/itachi85/article/details/50510124

在《Head First设计模式》的定义:

单例模式确保一个类只有一个实例,并提供一个全局访问点。

单例模式UML类图如下:

单例模式结构图

  1. 饿汉模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    public class Singleton{
    private static Singleton instance = new Singleton();
    private Singleton(){

    }
    public static Singleton getInstance(){
    return instance;
    }
    }

    优点:获取对象的速度快

    缺点:在类加载时就完成了初始化,所以类加载较慢

  2. 懒汉模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Singleton{
    private static Singleton instance;
    private Singleton(){

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

    优点:在用户第一次调用时初始化,节约资源

    缺点:第一次加载需要实例化,反应较慢,且在多线程不能正常工作

  3. 懒汉模式(线程安全)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    public class Singleton{
    private static Singleton instance;
    private Singleton(){

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

    优点:能在多线程中很好的工作

    缺点:每次调用getInstance函数时都需要进行同步,造成不必要的同步开销

  4. 双重检查模式(DCL)

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    public class Singleton{
    private static volatile Singleton instance;
    private Singleton(){

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

    下面这段分析来自原文:

    这种写法在getSingleton方法中对singleton进行了两次判空,第一次是为了不必要的同步,第二次是在singleton等于null的情况下才创建实例。 在这里使用volatile会或多或少的影响性能,但考虑到程序的正确性,牺牲这点性能还是值得的。 DCL优点是资源利用率高,第一次执行getInstance时单例对象才被实例化,效率高。缺点是第一次加载时反应稍慢一些,在高并发环境下也有一定的缺陷,虽然发生的概率很小。DCL虽然在一定程度解决了资源的消耗和多余的同步,线程安全等问题,但是他还是在某些情况会出现失效的问题,也就是DCL失效,在《java并发编程实践》一书建议用静态内部类单例模式来替代DCL。

  5. 静态内部类单例模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    public class Singleton{
    private Singleton(){

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

    第一次加载Singleton类时不会初始化instance,只有第一次调用getInstance函数时虚拟机加载SingletonHolder并初始化instance,这样不仅能确保线程安全也能保证Singleton类的唯一性。

  6. 使用枚举单例

    1
    2
    3
    4
    5
    6
    public enum Singleton{
    INSTANCE;
    public void doSomeThing(){

    }
    }

    默认枚举实例的创建是线程安全的,并且在任何情况下都是单例

  7. 使用容器实现单例模式

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    public class SingletonManager{
    private static Map<String, Object> map = new HashMap<String, Object>();
    private SingletonManager(){

    }
    public static void registerService(String key, Object instance){
    if(!map.containsKey(key)){
    map.put(key, instance);
    }
    }
    public Object getService(String key){
    return map.get(key);
    }
    }

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