V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
V2EX 提问指南
Samuelcc
V2EX  ›  问与答

Java 动态修改配置,关于并发可见性的问题

  •  
  •   Samuelcc · 2020-07-02 19:55:22 +08:00 · 1457 次点击
    这是一个创建于 1599 天前的主题,其中的信息可能已经有所发展或是发生改变。

    最近在写一个配置中心的客户端,碰到一个问题,没有想到特别好的解决方案,和大家讨论下。

    问题是这样的:

    当配置发生变更时,我想自动更新 spring bean 中的 @Value 字段。 看了下有一些开源实现用的是基于反射的方式,通过设置 field 的值来实现更新

    但是这样会有个问题,就是配置更新的线程修改了 field 的值,并不能保证用户其他线程可以看见最新的更改 (如果 field 上没有加 volatile,synchronized 这些保证可见性的关键字)。 但是不少开源实现好像都没有关注这个问题。

    我能想到的方法:

    • 让用户自己加 volatile 。这种最简单,但是需要让用户了解你的实现方式,算是比较侵入的方式,不透明。
    • 通过 java agent 修改字节码,为符合条件的字段添加 volatile 。这种容易实现,但是增加接入成本,需要修改启动参数,就为了这个功能,感觉不大划算。
    • 使用 annotation processor,通过和 lombok 类似的方式,直接在 pre compile 阶段修改源码。这种方式较难实现,并且比较 hack,毕竟 java 并不想让用户通过这种方式修改已存在的源码文件,它只是想让用户增加新的文件。

    最后的问题:

    • 为什么那些开源实现没有考虑并发可见性的情况?是我想错了吗?
    • 有什么比较好的方式能够在不侵入代码的前提下,保证修改配置后的并发可见性?
    16 条回复    2020-07-02 22:56:26 +08:00
    seaswalker
        1
    seaswalker  
       2020-07-02 20:40:57 +08:00
    讲道理在 X86 平台上这种修改就是可见的,根本就不需要 volatile....
    MoHen9
        2
    MoHen9  
       2020-07-02 20:41:58 +08:00 via Android   ❤️ 1
    想多了,你都需要动态修改配置了,还在乎一致性问题?如果真的考虑一致性,应该使用其他方式保证,比如缓存或 zk,而不是单纯的加锁和 volatile,一般服务可能会有集群,或者多个服务使用了相同的配置,这些服务之间的一致性需要保证吗?

    这种动态切换配置的做法一般在开发或部署时会用,甚至极端情况也会,这时候的一致性没有那么重要。
    sagaxu
        3
    sagaxu  
       2020-07-02 20:47:39 +08:00 via Android
    @seaswalker X86 上也不能保证吧
    seaswalker
        4
    seaswalker  
       2020-07-02 20:56:53 +08:00   ❤️ 1
    @sagaxu #3 我觉得除了写成下面这种不加 volatile 的循环导致编译器提升优化之外,x86 上应该是可见的
    写过几个例子测试过这个问题: https://github.com/seaswalker/JDK/issues/8
    楼主的场景编译器应该不会做这种优化,也可能我理解的有问题
    Samuelcc
        5
    Samuelcc  
    OP
       2020-07-02 21:02:02 +08:00
    @MoHen9 我倒不是想保证强一致性,只是想有一种保证,比如说配置刷新完成,这时候我可以认为这个实例中所有线程看到的配置都是最新的。
    如果不加 volatile,就无法做出保证,可能很久过去了,看到的还是旧版配置。
    Samuelcc
        6
    Samuelcc  
    OP
       2020-07-02 21:12:47 +08:00
    @seaswalker 学到了新知识,谢谢
    xiangyuecn
        7
    xiangyuecn  
       2020-07-02 21:15:16 +08:00
    一个 56 秒的操作,你在这纠结 0.0001ms ?
    sagaxu
        8
    sagaxu  
       2020-07-02 21:23:42 +08:00 via Android   ❤️ 1
    @seaswalker 你用了 System.out ,那玩意儿会加锁,约束比用了 volatile 更强
    Samuelcc
        9
    Samuelcc  
    OP
       2020-07-02 21:27:45 +08:00
    @xiangyuecn 不是时间的问题,只是想有保证,例如事务显示执行完成,这时候可以有保证不会丢失,而不是显示执行完成,但是可能在某个将来才落盘,哪怕这个将来在绝大多数情况下都很快
    sagaxu
        10
    sagaxu  
       2020-07-02 21:40:41 +08:00 via Android
    @seaswalker 除了加锁同步之外,你这段代码还有其他致命问题,根本不能用来验证并发问题。
    javapythongo
        11
    javapythongo  
       2020-07-02 21:42:31 +08:00 via iPhone
    spring 自带刷新,可以去了解下 @RefreshScope
    momocraft
        12
    momocraft  
       2020-07-02 21:45:33 +08:00
    实验只能证明并发不安全 不能证明并发安全
    Samuelcc
        13
    Samuelcc  
    OP
       2020-07-02 21:47:32 +08:00
    @javapythongo 这个我了解过,但是 spring 的 refresh 机制问题比较多,尤其是在 spring cloud 1.x 下,两次出线程泄漏的问题,不想用这个
    ipwx
        14
    ipwx  
       2020-07-02 21:53:32 +08:00
    @seaswalker 我觉得不一定吧。那种双 CPU 架构的服务器机器。。。
    seaswalker
        15
    seaswalker  
       2020-07-02 22:36:18 +08:00
    @sagaxu #8 我又写了个新的例子
    这次只是简单的加法,可以试一下,加上 JVM 参数-Xint,就会三秒后停止,去掉就会死循环,这不就说明了是 JIT 编译优化的锅?
    况且虽然打印中有锁,但子线程对 flag 的修改没有加锁,也不满足 happens-before 吧,我的理解是打印恰恰也起到了阻止 JIT 优化的作用
    sagaxu
        16
    sagaxu  
       2020-07-02 22:56:26 +08:00 via Android
    @seaswalker 首先,你的理解是错的。其次,即便你的理解是对的,难道要靠禁止 jit 来保证可见性?
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   实用小工具   ·   6200 人在线   最高记录 6679   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 31ms · UTC 06:17 · PVG 14:17 · LAX 22:17 · JFK 01:17
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.