单例的创建有两种方式:
1、非延迟加载:不管什么时候要使用,先提前创建实例。
2、延迟加载:等到真正要使用的时候才去创建实例,不用时不要去创建。
第一种:非延迟加载
public class Singleton {
private static final Singleton instance = new Singleton();
private Singleton() {
}
public static Singleton getInstance() {
return instance;
}
}
第二种:同步延迟加载
public class Singleton {
private static Singleton instance = null;
private Singleton() {
}
public static synchronized Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
多线程情况下,每一次获取,都需要获取同步锁,导致执行效率低下。
第三种:双重检测同步延迟加载
public class Singleton {
private volatile static Singleton instance = null;
private Singleton() {
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) { // 1
if (instance == null) { // 2
instance = new Singleton(); // 3 (旧版本:还没初始化完成,就已经对其它线程可见了)
}
}
}
return instance;
}
}
只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要获取同步锁了。
注意:JDK5.0以后版本中 instance 为 volatile,
双重检测同步延迟加载
才有效。
旧版本Java中, 双重检测同步延迟加载失败的主要原因是:Java内存模型中指令序列的“乱序执行”。
假设代码执行以下指令序列:
- 线程 1 进入 getInstance() 方法。
- 由于 instance 为 null,线程 1 在 //1 处进入 synchronized 块。
- 线程 1 前进到 //3 处,但在构造函数执行之前,使 instance 成为非 null。
- 线程 1 被线程 2 预占。
- 线程 2 检查 instance 是否为 null。因为 instance 不为 null,线程 2 将 instance 引用返回一个构造完整但部分初始化了的 Singleton 对象。
- 线程 2 被线程 1 预占。
- 线程 1 通过运行 Singleton 对象的构造函数并将引用返回给它,来完成对该对象的初始化。
为展示此情况的发生情况,假设代码行 instance =new Singleton(); 执行了下列伪代码:
mem = allocate(); //为单例对象分配内存空间
instance = mem; //instance 引用指向内存,此时 instance 不为 null,但还未初始化
construct(instance); //调用构造函数进行初始化
可以看出旧版本中, instance =new Singleton() 不具有操作的原子性。
可能原因如下(待验证):
Java 1.5 以前对实例变量是直接在主存中读写的,也就是以上 3 行伪代码的每一步操作,对于其它线程都是可见的。
Java 1.5 以后,JMM发生了根本性的改变,以上 3 行伪代码都在线程的工作空间中完成的,在没有向主存写入变量之前,其它线程对这个过程都是不可见的。volatile 关键字在这里有两个作用,一是禁止指令序列的“乱序执行”,让重排后的指令之间的依赖变得有序,必须让变量初始化完成后,才能够执行依赖于它的其它指令;二是执行完 3 行伪代码后,立即把变量 instance 写入到主存,该变量才对其它线程可见。
第四种:使用内部类实现延迟加载(推荐)
public class Singleton {
private Singleton() {
//实例的初始化
}
static class SingletonHolder {
static Singleton instance = new Singleton();
}
public static Singleton getInstance() {
return SingletonHolder.instance;
}
}
静态内部类SingletonHolder只会被初始化一次,因为在被类加载器加载后,对SingletonHolder的Class对象只会进行一次初始化。
第五种:枚举
public enum Singleton {
INSTANCE;
public void fun() {
}
}