V2EX = way to explore
V2EX 是一个关于分享和探索的地方
现在注册
已注册用户请  登录
guixiexiezou
V2EX  ›  Java

问个简单的 Java 重排序的问题

  •  
  •   guixiexiezou · 2020-05-08 22:04:54 +08:00 · 2801 次点击
    这是一个创建于 1441 天前的主题,其中的信息可能已经有所发展或是发生改变。

    也学了很久 java 了,但对重排序那块还是不太理解。直接上代码吧

    public class App {
        private static boolean flag = false;
        private static int cnt = 0;
        public static void main(String[] argv) throws Exception{
            new Thread(() -> {
                while (!flag) {
                    try {
                        // Thread.sleep(2000L);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    // System.out.println(cnt);
                }
                System.out.println("----a end");
            }).start();
            Thread.sleep(1000L);
            new Thread(() -> {
                refresh();
            }).start();
    
        }
        public static void refresh() {
            System.out.println("--stat");
            flag = true;
            cnt = 3;
            System.out.println("---end");
        }
    }
    

    执行这个代码,必然是不可以看到----a end这条输出的,但我们把上面的注释代码,随便取消注释 1 个,就可以看到输出----a end,然后程序结束。

    有大佬可以简单讲下经验吗

    17 条回复    2020-05-09 15:00:17 +08:00
    falsemask
        1
    falsemask  
       2020-05-08 22:40:29 +08:00   ❤️ 1
    https://www.zhihu.com/question/263528143/answer/270308453 和这个类似,这个应该是可见性的问题。Java 多线程可见性问题还挺复杂,我看的时候遇到了好几个问题都没找到答案
    zzl22100048
        2
    zzl22100048  
       2020-05-08 23:45:40 +08:00 via iPhone
    这是可见性问题吧,空循环不让 cpu 没法从主存同步变量
    avk458
        3
    avk458  
       2020-05-09 00:28:19 +08:00
    第一个显视线程注释掉那两行代码后就等于是空 while 了,好像叫做 busy-spin waiting 。这个线程会一直等待下去,当然也就看不到`----a end`吧?
    ffkjjj
        4
    ffkjjj  
       2020-05-09 09:54:00 +08:00
    我觉得, 因为对于 flag 变量, 我们没有显示的对它设置线程的同步, 编译器认为 flag 不会被多个线程共享, 因此代码被 JIT 优化了, 所以出现程序不结束的情况.

    ```java
    if(!flag){
    while (true) {
    try {
    // Thread.sleep(2000L);
    } catch (Exception e) {
    e.printStackTrace();
    }
    // System.out.println(cnt);
    }
    }

    ```
    如果关掉 JIT 编译, 程序就可以正常结束.
    ffkjjj
        5
    ffkjjj  
       2020-05-09 10:01:44 +08:00
    即使是空循环, 系统也不是把所有 cpu 执行时间都分配给此线程.
    125113483
        6
    125113483  
       2020-05-09 11:10:38 +08:00
    变量可见性问题 如果把 flag 加上 volatile 修饰就可以 结束循环 正常输出

    把代码 System.out.println 或 Thread.sleep 注释放开也可以结束循环 正常输出 是因为 println 方法被 synchronized 修饰 你可以点到方法里看一下 会刷新 flag 缓存 Thread.sleep 一样也会刷新 都是同步方法 如果你把这两个代码改成其他的 比如 cnt=2 这种非同步代码 一样会出现死循环
    yuxing1171
        7
    yuxing1171  
       2020-05-09 11:28:34 +08:00
    这与重排序有关系? 是可见性的问题吧,第一个线程一直 busy,没时间同步 flag 的值,而如果去过任意注释,CPU 就有时间同步 flag 的值。 用 volatile 修饰 flag,可以起到即时同步的目的。
    guixiexiezou
        8
    guixiexiezou  
    OP
       2020-05-09 11:58:38 +08:00
    @zzl22100048 原来如此,多谢了
    guixiexiezou
        9
    guixiexiezou  
    OP
       2020-05-09 11:58:54 +08:00
    @avk458 明白了,多谢
    guixiexiezou
        10
    guixiexiezou  
    OP
       2020-05-09 12:02:15 +08:00
    @ffkjjj 还真是,测试了下关掉 jit 确实是可以正常输出的,多谢啦
    guixiexiezou
        11
    guixiexiezou  
    OP
       2020-05-09 12:07:19 +08:00
    @125113483 多谢了,按照你的说法测试了下,确实会如此。但如果强制关闭 JIT,就会发发现可以结束了。所以我还是更倾向于是 JIT 的内部优化结果
    guixiexiezou
        12
    guixiexiezou  
    OP
       2020-05-09 12:10:04 +08:00
    @yuxing1171 确实是可见性的问题,昨晚说错了。另外你说的`第一个线程一直 busy,没时间同步 flag 的值`这个我是不认可的,安装其他楼层 说法,关掉 JIT 就可以正常结束。我倾向于这种说法,jvm 遇到这种空的死循环,压根就不会去同步数据(开启 JIT 的情况)
    ffkjjj
        13
    ffkjjj  
       2020-05-09 12:11:12 +08:00
    @guixiexiezou #10 但是这种说法, 我不知道怎么解释循环里面去掉注释之后可以同步的问题..
    ccpp132
        14
    ccpp132  
       2020-05-09 12:16:54 +08:00
    很简单啊,有别的函数调用的话,编译器就不能把 cnt 当作本地变量优化掉,相当于有 memory barrier 的作用。
    guixiexiezou
        15
    guixiexiezou  
    OP
       2020-05-09 12:23:57 +08:00
    @ccpp132 简单看了下,最主要的原因还是注释的代码都是同步方法,如果我们有别的函数调用的话(非同步方法),会发现结果是一样的,还是会死循环
    125113483
        16
    125113483  
       2020-05-09 14:50:29 +08:00
    @guixiexiezou 不是因为空的死循环不同步数据,你如果 volatile 修饰,空的死循环一样会同步啊 其实 sleep 也不是同步方法,他只是释放了 cpu 资源,cpu 资源一旦空闲 JVM 会完成优化 会同步工作内存和主存 完成内存的可见性
    ccpp132
        17
    ccpp132  
       2020-05-09 15:00:17 +08:00
    @guixiexiezou 是的,这些函数有 memory barrier 的效果,有些函数没有。和 cpu 资源没有关系的,内存可见性并不要 cpu 空闲。这种明显是没有 memory barrier 的提示被编译器或者 jit 直接优化掉了。如果是可见性的问题稍等就同步了,这个例子里是感觉不出来的。
    关于   ·   帮助文档   ·   博客   ·   API   ·   FAQ   ·   我们的愿景   ·   实用小工具   ·   5224 人在线   最高记录 6543   ·     Select Language
    创意工作者们的社区
    World is powered by solitude
    VERSION: 3.9.8.5 · 27ms · UTC 07:20 · PVG 15:20 · LAX 00:20 · JFK 03:20
    Developed with CodeLauncher
    ♥ Do have faith in what you're doing.