我们都知道下面的 DclSingleton.getInstance()是线程安全的。
public class DclSingleton {
private static volatile DclSingleton instance;
public static DclSingleton getInstance() {
if (instance == null) {
synchronized (DclSingleton .class) {
if (instance == null) {
instance = new DclSingleton();
}
}
}
return instance;
}
// private constructor and other methods...
}
但是我们去掉 volatile 的话,其它线程就有可能获取到 partially initialized instance 。
不过我有个疑问,要证明“加了 volatile 就是线程安全的,没加就不是线程安全”的话,测试代码应该怎么写呢?
我不是问有什么更好的写法,我是问怎么证明线程安全。因为Double-Checked Locking with Singleton | Baeldung已经讲了几种替代方案了。
1
edimetia3d 2022-05-09 22:09:58 +08:00
我是 C++ boy,
不太懂 java ,也不太懂 java 的内存模型,不过这里的 partial initialized 应该是指 instance 已经不是 null ,但是 DclSingleton()的构造函数还没有执行完。 思路上说,写一个特别耗时的构造函数,最后一步才更新一个 this.inited = True , 然后另外一个线程读取到 instance 不为 null,且 instance.inited = False 就行。 |
2
thinkershare 2022-05-09 22:13:56 +08:00
volatile 只是告诉 runtime 执行适合需要插入指令禁止乱序执行和缓存 instance 的引用地址(尽可能每次引用 instance 都需要从内存中去读取最新的值), 你要证明这个其实添麻烦的, 因为本质上是要撞运气, 至于你现在写的这个代码应该是不会存在 partially initialized instance 的, 因为 new DclSingleton()被正常构造完毕前, instance 是不会获得引用的, 除非你的 DclSingleton 是一个需要需要后序初始化的操作.
|
3
lmshl 2022-05-09 22:19:23 +08:00
改换思路,把 static 去了,换成一个对象的属性和方法。
写 JMH 循环创建几百万次,每次都多线程访问 getInstance ,记录下对象不相等的时刻。 |
4
Jooooooooo 2022-05-09 22:21:02 +08:00
很大可能, intel 的芯片上因为 cpu 足够强的一致性设计, 不加 volatile 也没事.
|
5
heiher 2022-05-09 22:25:11 +08:00 via Android
不加 volatile ,就可能发生 DclSingleton 对象的 filed 初始化赋值的 store 与 instance 的赋值的 store 乱序。测试逻辑就基于这个情况检测就行了,DclSingletion 增加 field 在构建函数中赋值,启动一批线程跑 getInstance 保存在局部变量,当获得的 instance 非空时,读 field 判断是否为有效赋值,最后再将 instance 复位为 null(也可专门开个复位线程来干)。这种要硬件乱序才能出问题的测试,很少次数就靠运气,但只要在弱内存序架构上多跑一会肯定会有的。
|
6
wolfie 2022-05-09 22:29:49 +08:00
循环跑呗,没轮重置 instance 为 null 。跑到重复为止。
|
7
heiher 2022-05-09 22:33:11 +08:00 via Android
我觉得你想优化掉 instance 的 volatile 减少非首次 getInstance 的开销,有个办法但要浪费点空间:
```java public class DclSingleton { private static DclSingleton instance; private static volatile DclSingleton _instance; public static DclSingleton getInstance() { if (instance == null) { synchronized (DclSingleton .class) { if (instance == null) { _instance = new DclSingleton(); instance = _instance; } } } return instance; } // private constructor and other methods... } ``` 记得也有 API 可以直接插入内存屏障,如果可以更好。 |
8
Suddoo 2022-05-09 23:07:59 +08:00 1
这些复杂的写法还有存在的意义吗? Java5 之后 enum 这种特殊的 class 出现了,要实现单例,就一行代码啊,单例的本质是限制内存中 class 的个数,enum 就可以干这个事
public enum Foo { INSTANCE; } https://stackoverflow.com/questions/70689/what-is-an-efficient-way-to-implement-a-singleton-pattern-in-java 编程本来是一件挺简单的事 |
9
heiher 2022-05-09 23:25:34 +08:00 via Android
|
10
dreamlike 2022-05-09 23:46:57 +08:00 via Android 2
https://github.com/openjdk/jcstress
openjdk 推荐测试用的是这个 |
11
Suddoo 2022-05-10 09:57:12 +08:00 via iPhone
|
12
heiher 2022-05-10 11:10:58 +08:00
@Suddoo #11 针对不要机理能跑就行的,我说了语法糖甜。针对需要机理的,我也说了展开的实现相对更容易了解。我能感受到你这段话的本意,但基本道理上并没有错。
|
14
zjp 2022-05-15 19:33:06 +08:00 1
|