这里一个好的思维方法是将线程分为两类:可以实例化类的线程和不能实例化类的线程。(为简洁起见,我将类名缩短为 just Singleton
)。那你就得想想每类线程需要做什么:
- 实例化线程需要存储它们创建的引用
instance
并返回它
- 所有其他线程都需要等待,直到
instance
已经设置,然后返回它
此外,我们需要确保两件事:
- 在实例化和所有返回(包括非实例化线程中的返回)之间存在先发生边缘。这是为了线程安全。
- 实例化线程的集合只有一个元素(当然,假设任一集合都是非空的)。这是为了确保只有一个实例。
好的,这就是我们的四个要求。现在我们可以编写满足它们的代码。
private final AtomicBoolean instantiated = new AtomicBoolean(false);
private static volatile Singleton instance = null;
// volatile ensures the happens-before edge
public static Singleton getInstance() {
// first things first, let's find out which category this thread is in
if (instantiated.compareAndSet(false, true) {
// This is the instantiating thread; the CAS ensures only one thread
// gets here. Create an instance, store it, and return it.
Singleton localInstance = new Singleton();
instance = localInstance;
return localInstance;
} else {
// Non-instantiating thread; wait for there to be an instance, and
// then return it.
Singleton localInstance = instance;
while (localInstance == null) {
localInstance = instance;
}
return localInstance;
}
}
现在,让我们说服自己满足我们的每一个条件:
- 实例化线程创建一个实例、存储它并返回它:这是 CAS 的“真正”块。
- 其他线程等待一个实例被设置,然后返回它:这就是
while
循环所做的。
- 在实例化和返回之间有一个先发生(HB)边缘:对于实例化线程,线程内语义确保了这一点。对于所有其他线程,
volatile
关键字确保写入(在实例化线程中)和读取(在此线程中)之间的 HB 边缘
- 假设该方法被调用过,实例化线程的集合正好是一个大的:第一个命中 CAS 的线程将让它返回 true;所有其他人都将返回false。
所以我们都准备好了。
这里的一般建议是将您的需求分解为尽可能具体的子需求。然后你可以分别解决每一个问题,这样更容易推理。