最粗糙的单例
public class Singleton {
private static Singleton instance;
public static Singleton getInstance(){
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
没有线程安全性可言
进行一次线程安全的改进版本
public class Singleton {
private static Singleton instance;
public static Singleton getInstance(){
if (instance == null) { // step 1
synchronized (instance) { // step2
instance = new Singleton(); // step3
}
}
return instance;
}
}
在new实例之前,我们对instance进行加锁,加锁成功后再去创建实例,看起来好像没问题了。可是,在线程A进行到step1到step2之间,线程B已经走完了step3,也就是说,线程A和线程B都检查到了instance还没有创建实例,两个线程就都会进行new操作,所以,我们还需要进行一次改进。
再一次完善线程安全的版本
public class Singleton {
private static Singleton instance;
public static Singleton getInstance(){
if (instance == null) { // step 1
synchronized (instance) { // step2
if (instance == null) { // step3
instance = new Singleton(); // step4
}
}
}
return instance;
}
}
完全ok了吗?
在上述代码中,instance = new Singleton();
是原子操作吗?
答案是否定的,通过查看字节码可以看到,刚才的代码涉及到三条字节码指令:
// 创建实例,分配内存
18: new #3 // class org/gh/demo/Singleton
21: dup
// 调用构造方法,进行初始化
22: invokespecial #4 // Method "<init>":()V
// 赋值给变量
25: putstatic #2 // Field instance:Lorg/gh/demo/Singleton;
上述三个指令,在jvm虚拟机实际运行时,22和25的顺序是可能发生改变的(关于指令重排序不在这里讲),也就是说有可能发生如下情况:
- 线程A获取到了锁并执行了new Singleton();
- 执行new Singleton();时,分配内存后,先进行了赋值引用给变量
- 线程B检查instance不为null,直接获取实例来进行使用,
发生异常
- 线程A进行初始化
所以我们还需要一次改进。
再次进化版本
public class Singleton {
private static volatile Singleton instance;
public static Singleton getInstance(){
if (instance == null) { // step 1
synchronized (instance) { // step2
if (instance == null) { // step3
instance = new Singleton(); // step4
}
}
}
return instance;
}
}
可以看到,这次的改动非常小,我们只需要在声明变量时,添加一个
volatile
关键字即可,该关键字的作用就是用来实现内存可见性与禁止重排序(关于volatile不在此展开)。
至此,我们一个完善的单例模式就完成了。