java - 创建单例问题的双重检查方式
问题描述
读取(关键资源块外)和写入(关键资源块内)如何不存在原子性问题。
我已经阅读并与不同的人讨论过,但大多数人没有回答这两个操作是否都是原子的,以及上述问题实际上是如何实现原子性的。
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 synchronisation block
和abcInstance = new ABC();
原子的,如果不是,那么这种创建单例的方式是错误的。
在 C++ 中,abcInstance = new ABC();
广义上由三个指令组成:
- 创建 ABC 对象。
- 为 ABC 分配内存。
- 将其分配给 abcInstance。
并且为了优化编译器可以以任何方式重新排序这三个指令。假设它遵循 2->3->1 并且在指令 3 中断发生之后,下一个调用 getInstance() 的线程将读取 abcInstance 有一些值,然后它将指向没有 ABC 对象的东西。
如果 C++ 和 Java 都错了,请纠正我。
解决方案
这仅回答了您问题的 Java 部分。
是
if(abcInstance == null)
并且abcInstance = new ABC();
是原子的,如果不是,那么这种创建单例的方式是错误的。
(潜在的)问题不是原子性。(从执行分配的线程和读取分配变量的线程的角度来看,引用分配是原子的。)
问题是当写入的值abcInstance
对另一个线程可见时。
在 Java 5 之前,内存模型并没有为该实现可靠地工作提供足够的内存可见性保证。
在 Java 5(及更高版本)内存模型中,一个线程对volatile变量的写入与另一个线程随后对该变量的读取之间存在着先发生关系。这表示:
abcInstance
如果第一个线程已写入,则保证第二个线程可以看到非空值。- 发生之前的关系还保证第二个线程将看到
ABC
由第一个线程创建的实例的完全初始化状态。 - 该
synchronized
块确保一次只能ABC
创建一个实例。
这是解释为什么旧的双重检查锁定实现被破坏的权威文章:
正如 Andrew Turner 所说,在 Java 中实现单例类有一种更简单、更简洁的方法:使用enum
.
推荐阅读
- python - 用python正则表达式从旧的计算出的新的替换子字符串
- android - 当它不是标准资源类型之一时,如何将现有文件安装到 apk 中?
- python - 如何在 django 框架中导入或调用 HTML 中的 PY 代码
- angular - Angular HttpClient 获取请求 URL 删除主题标签/数字符号
- google-analytics - 并非所有尺寸都可用
- ubuntu-18.04 - 无法使用 gnome builder 构建“polari”一个 gnome 应用程序
- keycloak - 如何解决我的 angular7 项目中的 keycloak refresh /#state - 我正在使用 keycloak-angular
- firebase - 为什么我的数组是 NaN 而我无法从 Firebase 数据库中获取价值?
- sass - SASS 用注释编译
- c# - 在 datagridview 中加载大量数据的性能问题