4

java | double check lock 问题

 1 year ago
source link: https://benpaodewoniu.github.io/2022/12/24/java141/
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

java | double check lock 问题

这个例子非常经典。

public final class Singleton {
private Singleton() { }
private static Singleton INSTANCE = null;

public static Singleton getInstance() {
if(INSTANCE == null) { // t2,这里的判断不是线程安全的
// 首次访问会同步,而之后的使用没有 synchronized
synchronized(Singleton.class) {
// 这里是线程安全的判断,防止其他线程在当前线程等待锁的期间完成了初始化
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}

double check lock 是一种提升多线程之间效率的表现。

public final class Singleton {
private Singleton() { }
private static Singleton INSTANCE = null;

public static Singleton getInstance() {
synchronized(Singleton.class) {
// 这里是线程安全的判断,防止其他线程在当前线程等待锁的期间完成了初始化
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
return INSTANCE;
}
}

那么,每次进来都需要进入 synchronized ,非常消耗资源。所以,在外层加了 INSTANCE 判断。

这样看似更合理,并且更快,但是,这样反而是线程不安全的。

这是因为 INSTANCE = new Singleton(); 这个代码的字节码是 4 个。

17: new 			#3 		// class test/Singleton
20: dup
21: invokespecial #4 // Method "<init>":()V
24: putstatic #2 // Field INSTANCE:Ltest/Singleton;
  • 17 表示创建对象,将对象引用入栈
  • 20 表示复制一份对象引用,引用地址
  • 21 表示利用一个对象引用,调用构造方法初始化对象
  • 24 表示利用一个对象引用,赋值给 static INSTANCE

步骤 2124 之间不存在数据依赖关系,而且无论重排前后,程序的执行结果在单线程中并没有改变,因此这种重排优化是允许的。

考虑这样一个情况

  • t1 刚进入,此时 INSTANCEnull ,其初始化对象
    • 由于重排问题,其先执行了 24 ,就是给 INSTANCE 付给了一个对象引用,但是,此时对象并没有创建
    • CPU 轮训
  • t2 进入,发现 INSTANCE 有了对象引用,开始使用 INSTANCE,但是,此时 INSTANCEnull

所以,我们要进行修改

public final class Singleton {
private Singleton() { }
private static volatile Singleton INSTANCE = null;

public static Singleton getInstance() {
if(INSTANCE == null) { // t2,这里的判断不是线程安全的
// 首次访问会同步,而之后的使用没有 synchronized
synchronized(Singleton.class) {
// 这里是线程安全的判断,防止其他线程在当前线程等待锁的期间完成了初始化
if (INSTANCE == null) {
INSTANCE = new Singleton();
}
}
}
return INSTANCE;
}
}

使用 volatile 修饰 INSTANCE,这是因为 volatile 具有写屏障和读屏障。可以保证 INSTANCE = new Singleton(); 执行的有序性,使其按照 17 - 20 - 21 - 24 的顺序执行。


About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK