Jiacheng

aha

野比大雄

单例模式的四种实现方式

Jiacheng / 2016-10-26


关于单例模式的概念就不说了,这里总结一下可以实际使用的四种实现方式。

1.饿汉式

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

这种实现方式会在类加载的时候初始化实例,而后每一次使用都会直接返回实例。

优点:实现简单,线程安全(由于类加载机制)

缺点:由于类加载的时候不一定是因为调用了 getInstance() 方法,所以可能会造成单例被提前实例化,没能实现懒加载。

2.静态内部类方式
public class Singleton {

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

这种方式主要是在饿汉式的基础上增加一个静态内部类,通过静态内部类持有单例类的实例。

优点:实现简单;线程安全(由于类加载机制);单例类初始化的时候并不会调用构造器,只有第一次调用getInstance()的时候才会通过静态内部类调用构造器,实现了了懒加载。

3.双重检查锁方式(懒汉式)
public class Singleton {  

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

这种方式的关键在于Java5 引进的 volatile 关键字。volatile 主要可以 避免编译器将变量缓存在寄存器以及避免编译器调整代码执行的顺序。

所以不使用这种关键字的其他写法的懒汉式都是不可靠的,会因为编译器调整代码的执行顺序而得到多个实例。

优点:线程安全(通过同步锁实现);只有调用getInstance()使用的时候才会构造实例,所以是懒加载。

缺点 :同步锁可能会造成效率的下降。

以上三种方式都有共同的缺点(参考《Effective Java》): 1.对于序列化的实例来说,需要处理反序列化创建新的实例的情况^[声明所有的实例域都是瞬时的(transient),并提供 readResolve 方法返回唯一的实例]。
2.需要防止通过反射调用构造器创建新的实例^[如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常]。

4.枚举式
public enum Singleton {
      INSTANCE;
}

这种方式是《Effective Java》这本书中推荐的实现单例的方式。另外枚举也是 Java5 引入的。

优点:实现简单,线程安全(由于类加载机制);枚举已提供序列化机制,可以避免由于反序列化而导致出现多个实例。

缺点:反编译后可以发现与饿汉式比较类似,所以并没有懒加载。

综上,对比来看,确实如《Effective Java》中所讲,使用枚举实现单例是最佳方法(可以防止反序列化以及反射造成的多实例)。不过在 Android 中,官方文档又明确指出枚举占用的内存是静态变量的两倍还多,要慎重使用枚举。所有,在某些时候,利用内部静态类的方式,配合手动处理反序列化以及反射的情况,也是不错的选择。