设计模式之 单例模式

 

  单例模式作为一种目标明确、结构简单、理解容易的设计模式,在软件开发中使用频率相当高,在很多应用软件和框架中都得以广泛应用。

 

有什么用?

  有的时候,对于一些对象,我们只需要一个或者说“唯一”,比方说:任务管理器、线程池、缓存、对话框、偏好设置和注册表的对象,日志对象,充当打印机、显卡等设备的驱动程序的对象。事实上,这类对象只能有一个实例,如果制造出多个实例,就会导致许多问题产生,例如:程序出错、资源使用过量、或者是得到不一致的结果。

  虽然很多时候,通过程序员之间做的约束、或者是利用全局变量(Java的静态变量),的确可以保证一个类只存在一个实例。但那样做不一定是最好的,我们需要一个更好的、通用的解决方案。而经过时间的考验,单件模式 可以确保一个对象只有一个实例被创建。单件模式也给了我们一个全局的访问点,和全局变量一样方便,又没有全局变量的缺点。

  全局变量的缺点,举例来说,如果将对象赋值给一个全局变量,那么你必须在程序一开始就创建好对象,若这个对象非常的耗费资源,而程序在这次的执行过程中又一直没用到它,不就形成浪费了吗?而利用单例模式,我们可以在需要的时候才创建对象。

  其实利用静态类变量、静态方法和适当的访问修饰符,的确也可以做到这一点。但是不管使用哪一种方法,我们都应该了解单例模式的运作方式和具体使用。

 

单例模式的实现可分为四大类:

  第一类,饿汉式(空间换时间)

最简单的实现方式

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

 

  第二类,懒汉式(时间换空间)

 线程不安全

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

线程安全

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

双检锁/双重校验锁(DCL,即 double-checked locking)

public class Singleton {  
    private volatile static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
        if (singleton == null) {  
            synchronized (Singleton.class) {  
                if (singleton == null) {  
                    singleton = new Singleton();  
                }  
            }  
        }  
        return singleton;  
    }  
}

通过volatile关键字防止指令重排序。

volatile关键字不但可以保证线程访问的变量值是主存中的最新值,而且可以防止指令重排。(非最佳实现,据说大部分JVM的implementation并不尊重volatile的规则,并且这个类可以通过反射构造多个实例对象。)

public class Singleton {
    private Singleton() {}

    private static volatile Singleton instance; 

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

 

  第三类,IoDH(initialization on demand holder) 

也可称静态内部类方式,延迟加载且线程安全(任何初始化失败都会导致单例类不可用,也就是说,IoDH这种实现方式只能用于能保证初始化不会失败的情况。)

加载一个类时,其内部类不会同时被加载。一个类被加载,当且仅当其某个静态成员(静态域、构造器、静态方法等)被调用时发生。

需要延迟加载时,推荐使用

public class Singleton {  
    private Singleton (){}  

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

 

  第四类,枚举

这种实现方式还没有被广泛采用,由于该方式的单例在面对复杂序列化以及反射攻击时,都能防止多次实例化,被称为单例模式的最佳实现。

不过,由于 JDK1.5 之后才加入 enum 特性,用这种方式写不免让人感觉生疏,在实际工作中,也很少用。不能通过 reflection attack 来调用私有构造方法。

public enum SingletonEnum {
      INSTANCE;
      public void doSomething() {
      }
  }

 

 

总结

  • 一个核心原理就是私有构造,并且通过静态方法获取一个实例。
  • 在这个过程中必须保证线程的安全性。
  • 推荐用静态内部内实现单例,或加了Volatile关键字的双重检查单例(JDK 1.5及以上版本)

 

参考:

  总结单例模式的几种实现方式及优缺点

  单例模式-菜鸟教程

 

共同学习,共同进步,若有补充,欢迎指出,谢谢!

posted @ 2019-08-22 12:22  逆水行舟,平原走马  阅读(137)  评论(0编辑  收藏  举报