如何读取(在关键资源 block 外)和写入(在关键资源 block 内)没有原子性问题。
我已经阅读并与不同的人讨论过,但大多数人没有回答这两个操作是否都是原子操作以及如何针对上述问题实际实现原子性。
class ABC {
private static volatile ABC abcInstance;
static ABC getInstance(){
if(abcInstance == null){
synchronized(ABC.class){
if(abcInstance == null){
abcInstance = new ABC();
return abcInstance;
}
}
}
return abcInstance;
}
}
if(abcInstance == null) outside synchronization block
和 abcInstance = new ABC();
是原子的,如果不是那么这种创建单例的方式是错误的。
在 C++ 中,abcInstance = new ABC();
从广义上讲包含三个指令:
- 创建 ABC 对象。
- 为 ABC 分配内存。
- 将其分配给 abcInstance。
为了优化,编译器可以以任何方式重新排列这三个指令。假设它遵循 2->3->1 并且在指令 3 中断发生后,下一个调用 getInstance() 的线程将读取 abcInstance 有一些值,然后它将指向没有 ABC 对象的东西。
如果 C++ 和 Java 都错了,请指正。
最佳答案
这仅回答了您问题的 Java 部分。
Is
if(abcInstance == null)
andabcInstance = new ABC();
are atomic, if not then this way of creating singleton is wrong.
(潜在的)问题不是原子性。 (从执行分配的线程和读取分配变量的线程的角度来看,引用分配是原子的。)
问题是当写入 abcInstance
的值对另一个线程可见时。
在 Java 5 之前,内存模型无法为该实现的可靠工作提供足够的内存可见性保证。
在 Java 5(及更高版本)内存模型中,一个线程对 volatile 变量的写入与另一个线程的后续读取之间存在发生在之间的关系的变量。这意味着:
- 如果第一个线程写入了
abcInstance
的非空值,则第二个线程保证看到它。 - happens before 关系还保证第二个线程将看到第一个线程创建的
ABC
实例的完全初始化状态。 synchronized
block 确保一次只能创建一个ABC
实例。
- 如果第一个线程写入了
这是解释为什么旧的双重检查锁定实现被破坏的权威文章:
正如 Andrew Turner 所说,有一种在 Java 中实现单例类的更简单、更清晰的方法:使用 enum
。
关于java - 双重检查创建单例问题的方式,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/57888409/