单例设计模式:保证某个类在所有的软件环境中只存在一个对象实例。这就是为什么它被叫做例。
在开始解释什么是单例设计模式之前,现来看一下它的实现方式:

实现单例模式的方式

  1. 饿汉式(静态常量)
  2. 饿汉式(静态代码块)
  3. 懒汉式(线程不安全)
  4. 懒汉式(线程安全,同步方法)
  5. 懒汉式(线程安全,同步代码块)
  6. 双重检查
  7. 静态内部类
  8. 枚举

    静态常量实现

    步骤:

  9. 私有化constructor

  10. 类的内部创建对象
  11. 暴露静态公共方法,例如:getInstance
  12. 实现上述方法

其中1,2意味着外部不能新建对象且仅能从内部直接访问该对象。3意味着这个方法调用只能返回同一个实例。

  1. class Singleton{
  2. // Step 1
  3. private Singleton(){}
  4. // Step 2
  5. private final static Singleton singleton = new Singleton();
  6. // Step 3 ,4
  7. public static Singleton getInstance(){return this.singleton;}
  8. }

静态常量实现__Singleton

Pros and Cons

  • Pros
    • 写法简单,在类装载时实例化,线程安全
  • Cons
    • 因为不是懒加载,如果使用频率不高,则会造成资源浪费

      在类装载时实例化意味着如果不能确定类装载的时机,那么getInstance可能不是唯一能够装载该类返回实例的方法。

静态代码块实现

和上一种实现方式相仿:

  1. class Singleton{
  2. private Singleton(){}
  3. private static Singleton singleton;
  4. static {
  5. singleton = new Singleton;
  6. }
  7. public static Singleton getInstance(){return this.singleton;}
  8. }

静态代码块实现__Singleton
应当注意的是,除了实现方式上略有区别,但静态代码块仍然同静态变量一样的优势和缺点。

懒汉式

线程不安全

为了解决单例不用就会造成资源浪费的问题,采用懒加载的策略可以对这一点进行优化,但要注意线程安全问题。

  1. class Singleton{
  2. private Singleton(){}
  3. private static Singleton singleton;
  4. public static Singleton getInstance(){
  5. if(singleton == null) this.singleton = new Singleton(); // Lazy load
  6. return singleton;
  7. }
  8. }

虽然看起来在getInstance时进行了判断,也确实起到了Lazy loading的效果,但是,同样由于进行了判断,在多个线程进行到if语句时,有可能会破坏单例模式的原则:只存在一个对象实例。

线程安全

在Java中,使用 synchronized 修饰 getInstance 方法可以保证只有一个线程可以访问到这一代码片段。
Easy and simple,right?需要注意,这样就表明了这是一个同步的方法。
同时,这里用 synchronized 修饰静态方法,作用于当前类对象加锁,进入同步代码前要获得当前类对象的锁。这一步骤给每一个想要调用该方法的线程都造成了效率损失,因此同步的线程安全法很多时候不符合需求。

synchronized是一种独占式的重量级锁,在运行到同步方法或者同步代码块的时候,让程序的运行级别由用户态切换到内核态,把所有的线程挂起,通过操作系统的指令,去调度线程。这样会频繁出现程序运行状态的切换,线程的挂起和唤醒,会消耗系统资源,为了提高效率,引入了偏向锁、轻量级锁、尽量让多线程访问公共资源的时候,不进行程序运行状态的切换。

此外,除了修饰静态方法,synchronize也可以修饰代码块,因此在实现getInsance时可以仅将创建对象的那部分修饰,然而由于if语句的关系,此时又是线程不安全的(尽管效率有可能比修饰静态方法的要高一点点,因为在修饰代码块时是指令级的),不推荐使用此方式。

Double Check

解决线程安全和运行效率,同时也是lazy loading。

  1. class Singleton{
  2. private Singleton(){}
  3. private static volatile Singleton singleton;
  4. public static Singleton getInstance(){
  5. if(singleton == null) {
  6. synchronized (Singleton.class){
  7. if(singleton == null) singleton = new Singleton();
  8. }
  9. }
  10. return singleton;
  11. }
  12. }

volatile 确保了变量的修改能被立即同步。
getInstance中使用了 synchronized 修饰代码段确保了线程安全。同时,在多线程情况下,由于方法并未声明同步,未持有锁的线程会在进行到第一个if条件时自旋/阻塞,而只有持有锁的线程才能继续判断第二个if语句并修改变量。
这里之所欲使用double check,就是因为只有修改变量是同步的,而且依靠 volatile 修饰的这个变量也会在新持有锁的线程进入临界区之前发生改变(被上一个线程改的),所以需要增加check。
而当已存在单例(也就是那个singleton)的时候,在第一个if条件就会返回,不需要额外的等待。

这里我隐约觉着其实和Java的锁的等级机制有些相仿,第一个if就是偏向锁,第二个if则是轻量级自旋锁。

以静态内部类实现Singleton

  1. class Singleton{
  2. private Singleton(){}
  3. // 静态内部类不会随着外部类的装载而装载
  4. // 当被调用时(在getInstance中)静态内部类才会以线程安全的方式加载
  5. private static class SingletonInstance{
  6. private static final Singleton SINGLETON = new Singleton();
  7. }
  8. public static synchronized Singleton getInstance(){
  9. return SingletonInstance.SINGLETON;
  10. }
  11. }

据上面的代码可以得出,静态内部类实现Singleton是线程安全的,且是lazy loading的。

以枚举实现Singelton

  1. enum Singleton{
  2. SINGLETON_1;
  3. public String foo(){return "foo";}
  4. }

枚举结构,单例模式,门当户对。
线程安全,且不会因反序列化重新创建对象。

Effective Java 作者 Josh Bloch推荐使用

JDK中的Singleton

Runtime.class 使用静态常量实现了单例。

注意事项

虽然实现单例的方式可能有很多,但是针对不同语言的实现方式和实用程度并不相同。在某些动态类型语言中,实现单例更多地需要参考语言特性。比如,在python中实现单例:

  1. class Singleton(object):
  2. def __new__(cls, *args, **kw):
  3. if not hasattr(cls, '_instance'):
  4. cls._instance = super().__new__(cls)
  5. return cls._instance
  6. class Foo(Singleton):
  7. x = 99
  8. a = Foo()
  9. b = Foo()
  10. id(a) == id(b)?print("Is singleton"):print("failed")

这种方式本质上类似于静态常量,只是因为python的属性没有实际的访问限制,所以在new处就要增加check,当然,这种方式存在着一些问题,我们已经讨论到了。

下面是使用装饰器的实现:

  1. class Singleton:
  2. instance = {}
  3. def __init__(self, cls):
  4. self.cls = cls
  5. def __call__(self, *args, **kw):
  6. if self.cls not in self.instance:
  7. self.instance[self.cls] = self.cls(*args, *kw)
  8. return self.instance[self.cls]
  9. @Singleton
  10. class Foo:
  11. def __init__(self):
  12. pass

当然,把某个类写成模块然后被其他模块导入也能起到单例的效果。

使用单例时往往会增加耦合度 。