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

4563博客

全新的繁體中文 WordPress 網站
  • 首頁
  • 关于指令重排序有个问题不明白,求大佬指点
未分類
5 2 月 2021

关于指令重排序有个问题不明白,求大佬指点

关于指令重排序有个问题不明白,求大佬指点

資深大佬 : svt 6

《 JAVA 并发编程的艺术》一书中说指令重排序可能会改变多线程程序的执行结果。举的例子如下:

class test { private int a = 0; private boolean flag = false;

public void change() {     a = 1;     flag = true; }  public void sysout() {     while (flag) {         System.out.println(a);     } } 

}

书中说以上程序在多线程时,输出的结果不一定是 1,有可能是 0 ; 但是我自己尝试了多次也未复现出这种情况,因此非常疑问这个情况真的可以复现出来吗?这个指令重排序到底说的真的假的呀?如果有复现出来的同学可以发个程序执行结果的图看下嘛?

大佬有話說 (23)

  • 資深大佬 : lcdtyph

    你需要在弱内存一致性的平台上(如 arm )来复现这个现象

  • 資深大佬 : lewis89

    说实在话 这个东西验证很困难,涉及到很多技术细节

    1. 你要确认 class 文件的字节码是否存在重排序,最好去翻一下字节码,一般这种简单的代码,
    我觉得可能字节码层面不会发生重排序

    2. 你要用这些代码 反复运行 让 JVM 生成 JIT 后的汇编指令,看下 JIT 后的汇编代码是否存在重排序

    3. CPU 层面还有分支预测跟指令冒险 这个更加不好验证

    4. 另外这里多线程的话 还要涉及到内存可见性的问题的,因为 CPU 的 L1 L2 L3 同步的模型很复杂,
    现在 X86 都是 TSO 模型,具体到 Intel 下面还有 MESI 的模型,另外 AMD zen2 架构流行后,
    现在又开始流行 NUMA 了,总之多线程同步下面的技术细节太多。

    有兴趣的话 你可以研究我的这篇博文,是关于内存可见性的研究,里面对 JVM 的 JIT 后的代码 用 GDB 打断点验证过了
    https://www.cnblogs.com/richard-winters/p/14237940.html

    关于重排序跟 memory barrier 这块,你只要记住 多线程同步的时候 一定要用 Java 的锁或者原子变量就行了,
    因为这两个都会用 x86 的同步原语 lock sfence mfence 等指令,如果不想搞清楚底层同步的原语,最好的办法就是记住 Java 的 happen before 原则就行了,这样多线程编程的时候就不会被底层的技术细节给搞蒙 B 了。

  • 資深大佬 : lewis89

    另外书上讲的不一定是发生了指令重排序,也有可能是内存可见性的问题,

    sysout() 线程可能根本就没观测到 change() 线程里面 对变量 a 的修改,因为 a 既没有 volatile ( x86 的 lock 原语),可能线程 sysout() 取到 a 的值 只是它在寄存器里面之前的拷贝,或者是线程 sysout() CPU 的 L1 的缓存,
    而且 sysout() 跟 change() 线程 都没有用到 同步的原语

  • 資深大佬 : lewis89

    @lewis89 #3 关于这些验证的猜想,都得像寻宝一样,设置各种运行条件,参考各种资料,然后到汇编代码层面去验证才能还原出结果来,对于初学者,我不建议大家这么去研究,一来这样研究的价值跟意义不大,应用层面的编程你只要遵循 Java 提供的抽象模型 Happen before 即可,二来如果你计算机体系结构基础不是很扎实的话,很难弄明白底层干了什么,当然如果你是反汇编 搞破解出生的,这些问题就能有办法研究清楚

  • 資深大佬 : hobochen

    JSR-133 能救你

  • 資深大佬 : sagaxu

    上都说的很全了,构造并发错误比写出没有并发问题的代码要困难的多,首先要深刻理解并发模型,然后要熟悉不同的处理器架构,选择合理的处理器架构(X86 经常不行),再避开一些会影响同步的测试代码,然后还需要一点运气。

    System.out.println 的内部实现自带一个锁,影响了并发语义,它是最容易犯的验证并发的错误。

  • 資深大佬 : fuse

    讲的都是啥,重排序这种词都是自己没懂,其他地方抄来的

    跟汇编根本没关系

    这个问题是 smp 情况下,cpu 对内存的写是异步的,一个 cpu 读到 flag 等于 true 时,a=1 这个写操作不一定就同步过来到这个 cpu 上,可能看到的还是 0

    为了保障逻辑正确,需要加同步等待指令

  • 資深大佬 : SingeeKing

    之前我亲测过( Intel Mac Oracle JDK 8 ),在 400 多万次的时候出来过一次

  • 資深大佬 : mind3x

    @lewis89 写得很详细。补充一下,javac 生成的字节码是完全阳春的,没有任何优化,也不会有任何重排序。

  • 資深大佬 : lewis89

    @fuse #7

    重排序是完全可能发生的,因为从编译器到 CPU 都是保证单线程 as-if-serial 语义,
    多线程的时候只能用到处理的同步原语才能解决

    int a = 2;
    int c = 1 + a;
    float b = 3f / 2f;

    像这种代码 第三条代码完全可能发生重排序,因为浮点运算会用到 FPU 而 CPU 完全是空闲出来的,
    而且从 CPU 优化的角度来看,第一条指令跟第二条有依赖,所以不会重排,但是 第三条指令跟 第一条 and 第二条指令不存在任何依赖,所以将 float b= 3f/2f 提前拿出来 交给 FPU 计算 得出结果是完全合理的,而且并不会影响程序的正确性,当然这一切都是在单线程的情况下,如果你多线程需要同步,那么必然要用 x86 lock mfence 这些原语来禁止 重排序

  • 資深大佬 : lewis89

    @mind3x #9 这个我就不了解了,从我了解的资料来看,包括我看 GCC 的编译后的汇编,都存在重排序优化的情况,所以我对 Java 字节码 也是一种猜测。

  • 資深大佬 : lewis89

    @sagaxu #6 system.out.println 这个我也研究过了 println 方法里面的 synchronize 关键字 在底层就用到了 同步的原语

  • 資深大佬 : YouLMAO

    c 也是会

  • 資深大佬 : agagega

    如果说是编译器对指令做的重排序优化的话,很简单:把一条除法语句放在一个不相关的加法后面,大多数平台上这个除法都会被调到前面去。

  • 資深大佬 : nuk

    和运行的 cpu 架构有关。。java 不保证顺序一致性

  • 資深大佬 : lewis89

    @nuk #15 几乎所有语言都只是 保证单线程自身的 as-if-serial 语义,因为到 CPU 层面 你也不知道 CPU 干了什么优化,CPU 也是保证单线程的 as-if-serial,除非你强制使用 内存栅栏去同步,这样 CPU 就会 invalid 掉 它的重排序

  • 資深大佬 : mxalbert1996

    @lewis89 重排序是 JIT 的事,完全没有必要在编译成字节码的时候再做一次重排序。

  • 資深大佬 : lewis89

    @mxalbert1996 #17 只是猜测,我也没验证过,我只是针对其它编译型语言 做出的猜测判断

  • 資深大佬 : minato

    这为啥会扯到指令重排?这不是 JMM 的常规可见性问题吗?

  • 主 資深大佬 : svt

    @lewis89 非常感谢您的发言

    @minato
    @fuse 你们两位说的是可见性的问题,不过我这个主要是为了探讨指令重排序,当然以上程序也有可能是因为可见性导致出现的输出 0 。我补充了重排序可能出现的情况。您可以看下。

  • 資深大佬 : Still4

    你这个例子没问题,指令重排会保证最终执行结果跟预期一样,但是不保证顺序,也就是说 change 方法执行完以后,a 一定=1,flag 一定=true,但是谁先谁后不一定

    这样一来,多线程环境下,监听线程读取到 flag 变成 true 的时候,a 的值有可能=0 有可能=1,不能按编码顺序去理解

  • 資深大佬 : icyalala

    单说指令的话:
    一个是编译器在编译时,指令可能会被重新排序,
    一个是 CPU 在 Out of Order 执行指令时,也可能会被重排。

    要说 Memory Model 那涉及到的东西就更多了。

  • 資深大佬 : zhch602

    @mxalbert1996 重排序并不只是 JIT 的事,CPU 指令流水线执行指令的时候也会指令重排的

文章導覽

上一篇文章
下一篇文章

AD

其他操作

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

51la

4563博客

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