- 问题描述:观察如下实现单例的代码,当完成实例创建之后,再次调用getInstance方法时,还是会进入到同步代码块内部,而同步代码块属于重量级锁,性能较低,因此需要避免创建完实例后,反复进入同步代码块的情况发生
public final class Singleton1 {
private Singleton1() {
}
private static Singleton1 INSTANCE = null;
public static Singleton1 getINSTANCE() {
synchronized (Singleton1.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton1();
}
return INSTANCE;
}
}
}
- 改进:使用双重锁,当未创建实例时,由同步代码块保证实例的单一。当创建完实例后,第一道锁(第一个if语句)可以避免代码反复进入到同步代码块之中
public final class Singleton1 {
private Singleton1() {
}
private static Singleton1 INSTANCE = null;
public static Singleton1 getINSTANCE() {
if (INSTANCE == null) {
synchronized (Singleton1.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton1();
}
}
}
return INSTANCE;
}
}
-
这样的改进在多线程环境下是有问题的,同步代码块内部可以保证原子性、顺序性、可见性,但是外部的if判断会出问题。详细分析
-
简略分析:INSTANCE = new Singleton1();这个赋值语句包含了:1、取Singleton1的引用地址 2、调用1中引用地址的newInstance方法 3、将1中的地址赋值给INSTANCE
-
其中执行顺序可以是123也可以是132,当JVM指令重排序结果为132时,假设t1线程执行完了1和3,这时cpu切换到t2线程,t2线程首先会去判断INSTANCE中的地址是否为空,由于第3步赋值了,因此if条件没有满足,因此会直接返回INSTANCE对象进行使用,但是线程t1还没有调用newInstance方法,就会发生错误
-
解决方法:在INSTANCE前面加上volatile关键字:阻止指令重排序
public final class Singleton1 {
private Singleton1() {
}
private static volatile Singleton1 INSTANCE = null;
public static Singleton1 getINSTANCE() {
if (INSTANCE == null) {
synchronized (Singleton1.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton1();
}
}
}
return INSTANCE;
}
}