关于 jmm 内存模型的问题

2020-05-14 20:37:50 +08:00
cmai  cmai

代码如下

 public static void main(String[] args) {
        Test a = new Test();
        a.start();
        for (; ; ) {
            if (a.isFlag()) {
                System.out.println("1");
            }
        }
    }

    static class Test extends Thread {

        private boolean flag = false;

        public boolean isFlag() {
            return flag;
        }

        @SneakyThrows
        @Override
        public void run() {
            Thread.sleep(1000);
            flag = true;
            System.out.println(flag);

        }
    }

背景

野生码仔,对这个问题困惑了一下午

我所了解的知识(不一定正确)

  1. jmm 内存模型中有主存和线程工作内存之分,线程读取一个变量会从主存读到工作内存之中,然后一切操作都是基于工作内存
  2. 工作内存是逻辑概念,实际可能是比如 cpu 缓存
  3. cpu 缓存一致性协议,如果有写操作的话,会通知主存此变量失效,然后其他线程有用这个变量的话会重新读取

代码执行结果

主线程中读到的 flag 值始终为 false

补充

代码改为如下,加上了 else

        for (; ; ) {
            if (a.isFlag()) {
                System.out.println("1");
            }else{
               System.out.println("2");
            }
        }

a 线程修改完 flag 值后,主线程是能拿到最新的值的

问题

  1. else 到底影响了主存和工作内存之间的哪些交互?
  2. 在没有 else 的情况下,a 线程修改了 flag 的值,main 线程的死循环里为何一直拿不到修改后的值

猜测

是否和 cpu 缓存使用的 mesi 协议有关?

2965 次点击
所在节点   程序员  程序员
45 条回复
cmai
cmai
2020-05-14 20:45:41 +08:00
期待答复
zhgg0
zhgg0
2020-05-14 20:52:06 +08:00
你点进 println 方法看下。
yeqizhang
yeqizhang
2020-05-14 20:56:02 +08:00
建议 javap 看下字节码
cmai
cmai
2020-05-14 20:56:54 +08:00
@zhgg0 感谢,看到了 sync,瞬间懂了。。。,是我疏忽了
cmai
cmai
2020-05-14 20:57:37 +08:00
1. else 到底影响了主存和工作内存之间的哪些交互?
本问题已结案,println 中用到了 sync
@zhgg0
cmai
2020-05-14 20:58:41 +08:00
2.在没有 else 的情况下,a 线程修改了 flag 的值,main 线程的死循环里为何一直拿不到修改后的值
现在只有这个问题了
cmai
2020-05-14 20:59:22 +08:00
@yeqizhang 我水平可能不太够,暂时还不能从这里下手
zifangsky
2020-05-14 21:08:12 +08:00
@cmai #6 因为你第一次的代码编译后是这样的:
public static void main(String[] args) {
Demo2.Test a = new Demo2.Test();
a.start();

while(true) {
while(!a.isFlag()) {
;
}

System.out.println("1");
}
}

然后这个 flag 修改后的值还对主线程是不可见的,所以主线程自然就一直死循环了。
xzg
2020-05-14 21:09:22 +08:00
你把 flag 定义 volatile 试下,我怀疑是子线程修改后没有及时刷新到主内存。
zifangsky
2020-05-14 21:14:08 +08:00
@xzg #9 就是你说的这个问题,子线程修改后的 flag 没有机会刷新到主内存,所以最简单的解决办法就是把 flag 变量用 volatile 修饰。
secondwtq
2020-05-14 21:20:13 +08:00
盲猜编译器是个好人
cmai
2020-05-14 21:21:57 +08:00
@zifangsky ok,感谢,这个我了解,但是其实我不是想问这个,因为 volatile 的话,线程对于改变量的操作,会加上内存屏障,从主存中获取, 但是如果我不加 volatile 的话, 我想问线程缓存的副本何时刷新到主存
cmai
2020-05-14 21:22:46 +08:00
并且其他用到该变量的线程何时从主存刷新到自己的线程副本
cmai
2020-05-14 21:24:07 +08:00
@secondwtq 让各位见笑了
cmai
2020-05-14 21:25:10 +08:00
@cmai fix: ok,感谢,这个我了解,但是其实我不是想问这个,因为 volatile 的话,所有线程对于该变量的操作,会加上内存屏障,从主存中获取, 但是如果我不加 volatile 的话, 我想问线程缓存的副本何时刷新到主存
cmai
2020-05-14 21:28:18 +08:00
@xzg 感谢,volatile/sync 是可以达到这样的效果,但是我的问题其实侧重于:主存和线程副本内存是怎么交互的,而不是如何才能达到线程通信的效果
xzg
2020-05-14 21:43:50 +08:00
@cmai 不是很明白,因为 jvm 何时刷新根据 cpu 指令之间的交互、cpu 调度等多种情况,具体你可能要看 jvm 源码了
Lonely
2020-05-14 21:49:44 +08:00
第二个问题,应该是即时编译器把 a.isFlag 优化掉了
momocraft
2020-05-14 22:02:14 +08:00
jmm 不保证的内存同步行为可能被具体 jvm 的具体版本 / 具体硬件 / jit / os 调度 影响

我怀疑研究这个的结论没意义, 就算知道了仍然没法面向这些不可控因素写 jawa 代码 (研究的过程可能有意义)
cmai
2020-05-14 22:27:14 +08:00
@momocraft 感谢回复,我认为搞懂 main 线程为何在死循环里始终读不到被 a 线程修改后的 flag 的值对我很有帮助,因为和我目前的认知产生了冲突,或者说是我的认知度太浅,所以想究其原因

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/671776

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX