前言:
单例模式是一种对象创建模式,确保系统中一个类只产生一个实例。本质上和池化技术的目的是一样的。
对于频繁使用的对象,可以省略创建和销毁的开销,尤其对于重量级对象而言。
其中重要的问题就是是否支持延迟加载,以及延迟加载带来的线程安全问题。
- 饿汉
由于instance是静态的,也就是类变量,所以会在类加载的初始化阶段就会生成实例。由于类加载一定是只执行一次的,当然他是线程安全的。具体可以看另一篇博文。
其最大的缺点就是,不能延迟加载,造成资源浪费。
public class Singleton {
private Singleton(){}
private static Singleton instance = new Singleton();
public static Singleton getSingleton() {
return this.instance;
}
}
- 懒汉
为了解决饿汉模式的延迟加载问题,就有了懒汉模式。但是由于存在线程安全问题,必须使用synchronized关键字实现线程同步。这就引来的新的问题,synchronized锁会严重影响程序的性能。
public class Singleton {
private Singleton(){}
private static Singleton instance;
public static synchronized Singleton getSingleton() {
if(instance == null) {
instance = new Singleton();
}
return instance;
}
}
- 双重校验锁
为了解决懒汉模式的性能问题。双重校验在生成实例的时候,会进行两次判断,大大降低了锁的概率。但是有一个问题,JVM会对程序进行优化,指令重排序。
创建实例的过程可以分为:分配内存空间、初始化对象、设置引用指向刚分配的内存。
那么指令重排序就可能造成实例赋值给instance时,还没有进行初始化。
我们需要使用volatile关键字禁止重排序。
public class Singleton {
private Singleton(){}
private static volatile Singleton instance;
public static Singleton getSingleton() {
if(instance == null) {
synchronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
- 静态内部类
上面解决指令重排序造成的问题时,使用volatile禁止了指令重排序。
其实还有另一种方法,就是允许指令重排序,但是重排序是对其他线程不可见的。
当我们首次调用getSingleton方法的时候,会触发InstanceHolder类的初始化。Java语言规范规定,对于每一个类或接口,都有一个唯一的初始化锁与之对应。
也就是说,当有多个线程初始化InstanceHolder类时,只有一个类获取到锁,这时候,即便初始化过程会进行重排序,但是这个重排序的中间状态对其他线程是不可见的。
public class Singleton {
private Singleton(){}
private static volatile Singleton instance;
private static class InstanceHolder {
public static Singleton instance = new Singleton();
}
public static Singleton getSingleton() {
return InstanceHolder.instance;
}
}
使用volatile的双重校验方式有一个额外的优势就是可以对实例字段实现延迟初始化。
- 枚举类
使用枚举类实现单例模式代码很简单。他是线程安全的,但是不支持延迟加载。
public enum Singleton {
INSTANCE;
public void getSingleton(){
return INSTANCE;
}
}
// 编译后等同于下面的类
public final class Singleton extends Enum<Singleton> {
public static final Singleton SINGLETON;
public static Singleton[] values();
public static Singleton valueOf(String s);
static {};
}
总结:
如果需要延迟加载,单例为类实例,推荐使用《4.静态内部类》的方法
如果需要延迟加载,单例为对象实例,推荐使用《3.双重校验锁》的方法
如果不需要延迟加载,推荐使用《5.枚举类》的方法