关于 volatile 可见性的一个问题 - V2EX
V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
mtmax
V2EX    Java

关于 volatile 可见性的一个问题

  •  1
     
  •   mtmax 2020-10-05 11:19:04 +08:00 3724 次点击
    这是一个创建于 1832 天前的主题,其中的信息可能已经有所发展或是发生改变。

    为啥线程读取了一个 volatile 变量 b, 居然能同时读到非 volatile 变量 a 的最新值

    static long a = 0; static long p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16; static volatile long b = 0; public static void main(String[] args)throws InterruptedException { new Thread(() -> { while (a == 0) { long x = b; // 为什么这里读 b, 能让线程同时读到 a 的最新值? 如果注释这行, a 就读不到 } System.out.println("a=" + a); }).start(); Thread.sleep(100); a = 1; } 
    25 条回复    2020-10-06 16:39:14 +08:00
    vk42
        1
    vk42  
       2020-10-05 11:26:54 +08:00
    这没什么问题啊,没有 volatile 只是不保证每次引用都会实际取值,又不是说肯定不会取值啊
    mtmax
        2
    mtmax  
    OP
       2020-10-05 11:37:54 +08:00
    @vk42 但是注释读 b 的那行后, 就没法读到 a 的最新值了
    我的理解是无法读取 a 的最新值是正确的, 因为 a 没有可见性
    但是问题是读 b 后, a 似乎具备了可见性, 这很奇怪
    momocraft
        3
    momocraft  
       2020-10-05 11:44:40 +08:00
    感是有明文保的行

    如果主程了 b,新程到 b 保到 (所有 happens-before ( b 的那次操作) 的果)。但是又有 b 。
    momocraft
        4
    momocraft  
       2020-10-05 11:46:10 +08:00
    可性是有形式定的
    +猜於 cargo cult,不如先看
    vk42
        5
    vk42  
       2020-10-05 11:49:32 +08:00
    @mtmax volatile 啥时候有可见性的意思? volatile 就是字面意思说明变量值“易变”,一般就是会被硬件或其它线程修改的变量。不给 a 加 volatile 的时候你说的两种情况都没啥问题,完全取决于编译器怎么处理
    littlewing
        6
    littlewing  
       2020-10-05 11:59:16 +08:00
    @vk42
    java 的 volatile 使用了内存屏障,确实有可见性的语义
    c/c++的 volatile 和 java 的语义不一样,只保证不被编译优化、指令重排和寄存器缓存,可见性和原子性不保证
    littlewing
        7
    littlewing  
       2020-10-05 12:00:07 +08:00
    关键字,java 、volatile 、memory barrier
    sagaxu
        8
    sagaxu  
       2020-10-05 12:06:07 +08:00 via Android
    干扰因素很多,
    1. System.out 内部加锁,自带线程同步
    2. Thread.sleep 等线程方法会不会也隐含同步?
    3. a=1 之后线程退出,有没有可能引起同步?

    @vk42 volatile 在 jvm 里有可见性保证
    vk42
        9
    vk42  
       2020-10-05 12:18:12 +08:00
    @littlewing @sagaxu 我对 JVM 内存模型确实不太了解,不过这个问题和原子性和 barrier 并没有关系。但 lz 的问题在于理解 volatile 保证可见性,不代表没有 volatile 变量就没有了可见性,应该说不加 volatile 的时候行为是不可确定的
    mtmax
        10
    mtmax  
    OP
       2020-10-05 12:19:35 +08:00
    @sagaxu
    1.System.out 前就已经读到 a=1 退出 while 循环了
    2.sleep 似乎没有可见性的保证, 就算有, 那么注释掉 long x = b 这行, 线程也应该退出 while 循环, 但实际上注释掉后就无法退出 while 循环
    3.同 2
    我觉得问题可能就在读 b 这行代码上, 具体不太清楚...
    mtmax
        11
    mtmax  
    OP
       2020-10-05 12:19:59 +08:00
    怀疑是内存屏障的原因
    sagaxu
        12
    sagaxu  
       2020-10-05 12:20:44 +08:00 via Android
    Synchronization actions, which are:

    Volatile read. A volatile read of a variable.

    Volatile write. A volatile write of a variable.

    Lock. Locking a monitor

    Unlock. Unlocking a monitor.

    The (synthetic) first and last action of a thread.

    Actions that start a thread or detect that a thread has terminated (§17.4.4).
    iseki
        13
    iseki  
       2020-10-05 12:29:33 +08:00
    首先 volatile 的保证是单方面的,保证加上能读到最新值,不保证不加上就一定读不到最新值。
    至于出现这个现象的原因可能是 volatile 用了内存屏障,这玩意儿会影响的粒度比较大,牵扯上了。
    iseki
        14
    iseki  
       2020-10-05 12:31:48 +08:00
    所以说这个问题其实牵扯到 JVM 底层对 volatile 的实现,属于规范以外的实现细节(不要面向这种东西编程
    sagaxu
        15
    sagaxu  
       2020-10-05 12:38:55 +08:00 via Android
    @mtmax 试试在 a=1 之后加一行
    Thread.sleep(1000)
    az467
        16
    az467  
       2020-10-05 13:02:11 +08:00
    > 如果注释这行, a 就读不到。

    这简单,你把 JIT 关掉就行了(如果你也是 open JDK )。
    估计是 JVM 直接帮你把 while ( a == 0 )替换成 while ( 0 == 0 )或者 while ( true )了。

    所以说这跟可见性根本就没有关系,只跟 JVM 的具体实现有关。
    octobered
        17
    octobered  
       2020-10-05 14:10:12 +08:00
    用 gdb 搞了一下,确实是 @az467 说的这样子的,设置了 -Djava.compiler=NONE 就可以解决了
    具体拿 gdb 反汇编出来是这样的
    0x7f714b23cfec: movabs $0x45044ff28,%r10
    0x7f714b23cff6: mov 0x70(%r10),%r10 // 稍晚时候看,0x45044ff28+0x70 这个位置确实已经是 1 了
    0x7f714b23cffa: test %r10,%r10 // 比较是否为 0 只比了这么一次
    0x7f714b23cffd: jne 0x7f714b23d00b
    0x7f714b23cfff: mov 0x108(%r15),%r10 // 之后都是从$r15+0x108 这个地方读,而这里一直是 0
    => 0x7f714b23d006: test %eax,(%r10)
    0x7f714b23d009: jmp 0x7f714b23cfff
    0x7f714b23d00b: mov $0xffffff7e,%esi

    具体为什么是从$15+0x108 读,有无大佬来解释一下,是 jit 导致的吗
    Wicked
        18
    Wicked  
       2020-10-05 16:08:10 +08:00
    建议先了解一下 指令乱序,内存屏障,store release,load acquire 等基础概念,然后再去看手册

    否则还是老老实实用更高层的同步机制吧,如果不是性能瓶颈,lock 就足够了
    zhgg0
        19
    zhgg0  
       2020-10-05 20:59:34 +08:00
    while (a == 0) {
    long x = b; // 为什么这里读 b, 能让线程同时读到 a 的最新值? 如果注释这行, a 就读不到
    }
    zhgg0
        20
    zhgg0  
       2020-10-05 21:02:50 +08:00
    while (a == 0) {
    long x = b; // 为什么这里读 b, 能让线程同时读到 a 的最新值? 如果注释这行, a 就读不到
    }
    没有 long x = b; 这行的话,jvm 会优化这几句代码,可能根本就不执行这个死循环。
    Weixiao0725
        21
    Weixiao0725  
       2020-10-06 07:37:50 +08:00
    因为 a 只是普通变量,什么时候刷新到内存要看运行时。当你加上读取 b 的那句代码时,因为 b 是 volatile 的,会强制从内存读,所以这时候强制把 a 的内容重新刷到了内存中,所以这时候就可以读取到最新的 a 值了
    matt5ttam
        22
    matt5ttam  
       2020-10-06 11:10:03 +08:00 via iPhone
    这个是缓存一致性协议造成的 volatile 会使用 lock#锁总线
    Newyorkcity
        23
    Newyorkcity  
       2020-10-06 15:22:11 +08:00
    我本来想说会不会是缓存行(字)的问题..

    但楼主的代码里之所以会有

    static long p1, p2, p3, p4, p5, p6, p7, p8, p9, p10, p11, p12, p13, p14, p15, p16;

    想必也是考虑到这个方向了吧....而且看这个样子应该也不是缓存行的问题了.
    letianqiu
        25
    letianqiu  
       2020-10-06 16:39:14 +08:00
    @Wicked 这么多楼就你一个明白人。
    关于     帮助文档     自助推广系统     博客     API     FAQ     Solana     2666 人在线   最高记录 6679       Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 26ms UTC 02:26 PVG 10:26 LAX 19:26 JFK 22:26
    Do have faith in what you're doing.
    ubao snddm index pchome yahoo rakuten mypaper meadowduck bidyahoo youbao zxmzxm asda bnvcg cvbfg dfscv mmhjk xxddc yybgb zznbn ccubao uaitu acv GXCV ET GDG YH FG BCVB FJFH CBRE CBC GDG ET54 WRWR RWER WREW WRWER RWER SDG EW SF DSFSF fbbs ubao fhd dfg ewr dg df ewwr ewwr et ruyut utut dfg fgd gdfgt etg dfgt dfgd ert4 gd fgg wr 235 wer3 we vsdf sdf gdf ert xcv sdf rwer hfd dfg cvb rwf afb dfh jgh bmn lgh rty gfds cxv xcv xcs vdas fdf fgd cv sdf tert sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf sdf shasha9178 shasha9178 shasha9178 shasha9178 shasha9178 liflif2 liflif2 liflif2 liflif2 liflif2 liblib3 liblib3 liblib3 liblib3 liblib3 zhazha444 zhazha444 zhazha444 zhazha444 zhazha444 dende5 dende denden denden2 denden21 fenfen9 fenf619 fen619 fenfe9 fe619 sdf sdf sdf sdf sdf zhazh90 zhazh0 zhaa50 zha90 zh590 zho zhoz zhozh zhozho zhozho2 lislis lls95 lili95 lils5 liss9 sdf0ty987 sdft876 sdft9876 sdf09876 sd0t9876 sdf0ty98 sdf0976 sdf0ty986 sdf0ty96 sdf0t76 sdf0876 df0ty98 sf0t876 sd0ty76 sdy76 sdf76 sdf0t76 sdf0ty9 sdf0ty98 sdf0ty987 sdf0ty98 sdf6676 sdf876 sd876 sd876 sdf6 sdf6 sdf9876 sdf0t sdf06 sdf0ty9776 sdf0ty9776 sdf0ty76 sdf8876 sdf0t sd6 sdf06 s688876 sd688 sdf86