文章目录
- double-checked locking(DCL) 问题
- 解决方法
- volatile作用
double-checked locking(DCL) 问题
- 第一个if用于后续进入的线程,不用再获取锁来判断是否已经创建了对象。
- 第二个if,为的是第一个进入的线程创建对象,以及防止卡在第一个if之后,获锁之前的线程在第一个线程已经创建对象的情况下,在获取锁后,判断不用创建对象,防止多次创建。
单例模式:
public final class Singleton {
private Singleton() { }
private static Singleton INSTANCE = null;
public static Singleton getInstance() {
if(INSTANCE == null) {
// 首次访问同步,而之后的使用就不用 synchronized,所以在此行前加了判断
synchronized(Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
特点:
- 懒汉式实例化
- 首次使用加锁,之后不用加锁
但是,看似完美的代码,其实并不对,因为第一个if判断并不在synchronized中,所以可能会发生指令重排问题。
synchronized可以保证原子、有序、可见性,但是有序性需要变量完全synchronized被保护,这里第一个if并不在sync中,所以是可能发生下述情况的。
也就是说synchronized只能保证临界区内的指令不和临界区外的指令发生重排序。
比如先给INSTANCE赋值了,但是还没有调用构造方法,这时线程切换,他以为你创建好了对象,然后返回对象直接开始使用,是不是就出现问题了,因为我们对象还没构造呢。
解决方法
给Singleton加volatile。
public final class Singleton {
private Singleton() { }
private static volatile Singleton INSTANCE = null;
public static Singleton getInstance() {
if(INSTANCE == null) {
// 首次访问同步,而之后的使用就不用 synchronized,所以在此行前加了判断
synchronized(Singleton.class) {
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}
这样就是一个正确的代码了。
volatile作用
volatile作用:
可见性:
- 写屏障(sfence)保证在该屏障之前对共享变量的改动,都同步到主存当中
- 读屏障(lfence)保证在该屏障之后对共享变量的读取,加载的是主存中最新数据
有序性:
- 写屏障会确保指令重排序时,不会将写屏障之前的代码排在写屏障之后
- 读屏障会确保指令重排序时,不会将读屏障之后的代码排在读屏障之前
只能保证读数据正确,不能保证原子性。