V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
JasonLaw
V2EX  ›  程序员

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

  •  
  •   JasonLaw · 2022-05-09 21:01:57 +08:00 · 1393 次点击
    这是一个创建于 689 天前的主题,其中的信息可能已经有所发展或是发生改变。

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


    资料

    第 1 条附言  ·  2022-05-10 11:37:50 +08:00

    我不是问有什么更好的写法,我是问怎么证明线程安全。因为Double-Checked Locking with Singleton | Baeldung已经讲了几种替代方案了。

    14 条回复    2022-05-15 19:33:06 +08:00
    edimetia3d
        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 就行。
    thinkershare
        2
    thinkershare  
       2022-05-09 22:13:56 +08:00
    volatile 只是告诉 runtime 执行适合需要插入指令禁止乱序执行和缓存 instance 的引用地址(尽可能每次引用 instance 都需要从内存中去读取最新的值), 你要证明这个其实添麻烦的, 因为本质上是要撞运气, 至于你现在写的这个代码应该是不会存在 partially initialized instance 的, 因为 new DclSingleton()被正常构造完毕前, instance 是不会获得引用的, 除非你的 DclSingleton 是一个需要需要后序初始化的操作.
    lmshl
        3
    lmshl  
       2022-05-09 22:19:23 +08:00
    改换思路,把 static 去了,换成一个对象的属性和方法。
    写 JMH 循环创建几百万次,每次都多线程访问 getInstance ,记录下对象不相等的时刻。
    Jooooooooo
        4
    Jooooooooo  
       2022-05-09 22:21:02 +08:00
    很大可能, intel 的芯片上因为 cpu 足够强的一致性设计, 不加 volatile 也没事.
    heiher
        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(也可专门开个复位线程来干)。这种要硬件乱序才能出问题的测试,很少次数就靠运气,但只要在弱内存序架构上多跑一会肯定会有的。
    wolfie
        6
    wolfie  
       2022-05-09 22:29:49 +08:00
    循环跑呗,没轮重置 instance 为 null 。跑到重复为止。
    heiher
        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 可以直接插入内存屏障,如果可以更好。
    Suddoo
        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

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

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

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

    自动挡的车也不用开了,开手动档的更有利于了解汽车背后运行的机理
    heiher
        12
    heiher  
       2022-05-10 11:10:58 +08:00
    @Suddoo #11 针对不要机理能跑就行的,我说了语法糖甜。针对需要机理的,我也说了展开的实现相对更容易了解。我能感受到你这段话的本意,但基本道理上并没有错。
    JasonLaw
        13
    JasonLaw  
    OP
       2022-05-10 11:36:03 +08:00
    @heiher #9 加上 final 是不行的,这样的静态变量是需要初始化的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   2899 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 26ms · UTC 11:23 · PVG 19:23 · LAX 04:23 · JFK 07:23
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.