怎么写测试代码证明 double-checked locking with singleton 的线程安全?

2022-05-09 21:01:57 +08:00
 JasonLaw

我们都知道下面的 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 就是线程安全的,没加就不是线程安全”的话,测试代码应该怎么写呢?


资料

1539 次点击
所在节点    程序员
14 条回复
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 就行。
thinkershare
2022-05-09 22:13:56 +08:00
volatile 只是告诉 runtime 执行适合需要插入指令禁止乱序执行和缓存 instance 的引用地址(尽可能每次引用 instance 都需要从内存中去读取最新的值), 你要证明这个其实添麻烦的, 因为本质上是要撞运气, 至于你现在写的这个代码应该是不会存在 partially initialized instance 的, 因为 new DclSingleton()被正常构造完毕前, instance 是不会获得引用的, 除非你的 DclSingleton 是一个需要需要后序初始化的操作.
lmshl
2022-05-09 22:19:23 +08:00
改换思路,把 static 去了,换成一个对象的属性和方法。
写 JMH 循环创建几百万次,每次都多线程访问 getInstance ,记录下对象不相等的时刻。
Jooooooooo
2022-05-09 22:21:02 +08:00
很大可能, intel 的芯片上因为 cpu 足够强的一致性设计, 不加 volatile 也没事.
heiher
2022-05-09 22:25:11 +08:00
不加 volatile ,就可能发生 DclSingleton 对象的 filed 初始化赋值的 store 与 instance 的赋值的 store 乱序。测试逻辑就基于这个情况检测就行了,DclSingletion 增加 field 在构建函数中赋值,启动一批线程跑 getInstance 保存在局部变量,当获得的 instance 非空时,读 field 判断是否为有效赋值,最后再将 instance 复位为 null(也可专门开个复位线程来干)。这种要硬件乱序才能出问题的测试,很少次数就靠运气,但只要在弱内存序架构上多跑一会肯定会有的。
wolfie
2022-05-09 22:29:49 +08:00
循环跑呗,没轮重置 instance 为 null 。跑到重复为止。
heiher
2022-05-09 22:33:11 +08:00
我觉得你想优化掉 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 可以直接插入内存屏障,如果可以更好。
Suddoo
2022-05-09 23:07:59 +08:00
这些复杂的写法还有存在的意义吗? 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

编程本来是一件挺简单的事
heiher
2022-05-09 23:25:34 +08:00
@Suddoo 语法糖就是甜呀,哈哈,但也更难看清背后机理了(要啥机理,能跑就行 :P )。他这不仅是 Singleton 还要 Lazy Initialization 。

@JasonLaw 其实用 final 修饰最好,对于写端包含 final 字段的对象都是安全初始化的,也就是说对象成员字段在初始化时的赋值 store 一定在对象地址写入引用变量的 store 前可见。对于读端 final
instance 可以按常量来优化。
dreamlike
2022-05-09 23:46:57 +08:00
https://github.com/openjdk/jcstress
openjdk 推荐测试用的是这个
Suddoo
2022-05-10 09:57:12 +08:00
@heiher 这么喜欢背后的机理,为啥不直接用沙子做 cpu 呢?

为啥还要用计算机呢,用算盘不是更好吗?

自动挡的车也不用开了,开手动档的更有利于了解汽车背后运行的机理
heiher
2022-05-10 11:10:58 +08:00
@Suddoo #11 针对不要机理能跑就行的,我说了语法糖甜。针对需要机理的,我也说了展开的实现相对更容易了解。我能感受到你这段话的本意,但基本道理上并没有错。
JasonLaw
2022-05-10 11:36:03 +08:00
@heiher #9 加上 final 是不行的,这样的静态变量是需要初始化的。
zjp
2022-05-15 19:33:06 +08:00

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/851832

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX