跳至主要內容
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 关于 volatile 可见性的一个问题
未分類
4 10 月 2020

关于 volatile 可见性的一个问题

关于 volatile 可见性的一个问题

資深大佬 : mtmax 3

为啥线程读取了一个 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)

  • 資深大佬 : vk42

    这没什么问题啊,没有 volatile 只是不保证每次引用都会实际取值,又不是说肯定不会取值啊

  • 主 資深大佬 : mtmax

    @vk42 但是注释读 b 的那行后, 就没法读到 a 的最新值了
    我的理解是无法读取 a 的最新值是正确的, 因为 a 没有可见性
    但是问题是读 b 后, a 似乎具备了可见性, 这很奇怪

  • 資深大佬 : momocraft

    感覺是沒有明文保證的行爲

    如果主線程寫了 b,新線程讀到 b 時應保證讀到 (所有 happens-before (寫 b 的那次操作) 的寫結果)。但是這裏又沒有寫 b 。

  • 資深大佬 : momocraft

    可見性是有形式定義的
    實驗+猜屬於 cargo cult,不如先看標準

  • 資深大佬 : vk42

    @mtmax volatile 啥时候有可见性的意思? volatile 就是字面意思说明变量值“易变”,一般就是会被硬件或其它线程修改的变量。不给 a 加 volatile 的时候你说的两种情况都没啥问题,完全取决于编译器怎么处理

  • 資深大佬 : littlewing

    @vk42
    java 的 volatile 使用了内存屏障,确实有可见性的语义
    c/c++的 volatile 和 java 的语义不一样,只保证不被编译优化、指令重排和寄存器缓存,可见性和原子性不保证

  • 資深大佬 : littlewing

    关键字,java 、volatile 、memory barrier

  • 資深大佬 : sagaxu

    干扰因素很多,
    1. System.out 内部加锁,自带线程同步
    2. Thread.sleep 等线程方法会不会也隐含同步?
    3. a=1 之后线程退出,有没有可能引起同步?

    @vk42 volatile 在 jvm 里有可见性保证

  • 資深大佬 : vk42

    @littlewing @sagaxu 我对 JVM 内存模型确实不太了解,不过这个问题和原子性和 barrier 并没有关系。但 lz 的问题在于理解 volatile 保证可见性,不代表没有 volatile 变量就没有了可见性,应该说不加 volatile 的时候行为是不可确定的

  • 主 資深大佬 : mtmax

    @sagaxu
    1.System.out 前就已经读到 a=1 退出 while 循环了
    2.sleep 似乎没有可见性的保证, 就算有, 那么注释掉 long x = b 这行, 线程也应该退出 while 循环, 但实际上注释掉后就无法退出 while 循环
    3.同 2
    我觉得问题可能就在读 b 这行代码上, 具体不太清楚…

  • 主 資深大佬 : mtmax

    怀疑是内存屏障的原因

  • 資深大佬 : sagaxu

    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

    首先 volatile 的保证是单方面的,保证加上能读到最新值,不保证不加上就一定读不到最新值。
    至于出现这个现象的原因可能是 volatile 用了内存屏障,这玩意儿会影响的粒度比较大,牵扯上了。

  • 資深大佬 : iseki

    所以说这个问题其实牵扯到 JVM 底层对 volatile 的实现,属于规范以外的实现细节(不要面向这种东西编程

  • 資深大佬 : sagaxu

    @mtmax 试试在 a=1 之后加一行
    Thread.sleep(1000)

  • 資深大佬 : az467

    > 如果注释这行, a 就读不到。

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

    所以说这跟可见性根本就没有关系,只跟 JVM 的具体实现有关。

  • 資深大佬 : octobered

    用 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

    建议先了解一下 指令乱序,内存屏障,store release,load acquire 等基础概念,然后再去看手册

    否则还是老老实实用更高层的同步机制吧,如果不是性能瓶颈,lock 就足够了

  • 資深大佬 : zhgg0

    while (a == 0) {
    long x = b; // 为什么这里读 b, 能让线程同时读到 a 的最新值? 如果注释这行, a 就读不到
    }

  • 資深大佬 : zhgg0

    while (a == 0) {
    long x = b; // 为什么这里读 b, 能让线程同时读到 a 的最新值? 如果注释这行, a 就读不到
    }
    没有 long x = b; 这行的话,jvm 会优化这几句代码,可能根本就不执行这个死循环。

  • 資深大佬 : Weixiao0725

    因为 a 只是普通变量,什么时候刷新到内存要看运行时。当你加上读取 b 的那句代码时,因为 b 是 volatile 的,会强制从内存读,所以这时候强制把 a 的内容重新刷到了内存中,所以这时候就可以读取到最新的 a 值了

  • 資深大佬 : matt5ttam

    这个是缓存一致性协议造成的 volatile 会使用 lock#锁总线

  • 資深大佬 : Newyorkcity

    我本来想说会不会是缓存行(字)的问题..

    但主的代码里之所以会有

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

    想必也是考虑到这个方向了吧….而且看这个样子应该也不是缓存行的问题了.

  • 資深大佬 : letianqiu

    https://docs.oracle.com/javase/specs/jls/se11/html/jls-17.html#jls-17.4.5

  • 資深大佬 : letianqiu

    @Wicked 这么多就你一个明白人。

文章導覽

上一篇文章
下一篇文章

AD

其他操作

  • 登入
  • 訂閱網站內容的資訊提供
  • 訂閱留言的資訊提供
  • WordPress.org 台灣繁體中文

51la

4563博客

全新的繁體中文 WordPress 網站
返回頂端
本站採用 WordPress 建置 | 佈景主題採用 GretaThemes 所設計的 Memory
4563博客
  • Hostloc 空間訪問刷分
  • 售賣場
  • 廣告位
  • 賣站?
在這裡新增小工具