为什么这个java程序会挂起?

问题描述 投票:2回答:2

以下程序挂起,但如果我取消注释System.out.printf它正常退出。我想知道为什么会这样。提前致谢。

public class MainClass{
    public static class Node{
        public Integer val;

        Node() {
            val = new Integer(0);
        }

        public void hang() {
            int i = 0;
            while(this.val != 1) {
                // the program quit normally if I uncomment the following line
                //System.out.printf("");
                i++;
            }
            System.out.println(i);
            System.out.println("quit");
        }

        private int func(int i) {
            return i+1;
        }
    }

    public static void main(String[] args) throws InterruptedException{
        Node n = new Node();
        Thread t = new Thread(new Runnable(){
            @Override
            public void run() {
                n.hang();
            }
        });

        t.start();

        // wait for t to start
        Thread.sleep(500);

        n.val = new Integer(1);

        Thread.sleep(500);

        t.join();
    }
}

java -version的输出

openjdk version "1.8.0_191"
OpenJDK Runtime Environment (build 1.8.0_191-8u191-b12-0ubuntu0.16.04.1-b12)
OpenJDK 64-Bit Server VM (build 25.191-b12, mixed mode)
java
2个回答
5
投票

我认为这是因为值可能会被复制到CPU缓存中,这意味着多个线程可能会为同一个变量设置不同的值。为了防止这种情况,您可以告诉java使用主内存。请将“public Integer val”更改为“public volatile Integer val”,并观察该程序现在退出。

更多关于挥发性的信息:When to use volatile with multi threading?

当你问为什么会发生这种情况时:多线程是一种你应该假设发生的事情是随机的。我的猜测是printf中的线程等待一些IO资源,等待时它被挂起,但是当它恢复时,缓存会先刷新。但正如我刚才所说,这只是一个猜测,因为你不知道什么时候进行不同步的线程操作


1
投票

解决方案显示在maslan的答案中。但是,至于你的问题,我认为添加'System.out.printf'的原因将停止悬挂,如果没有,JIT编译器将转换代码,如:

if (this.val != 1) while(true) {i++;}

而且我想添加调用会阻止JIT进行优化。因此,当线程决定从内存中读取而不使用缓存时,程序会正常退出。

我猜测的一个证明是,通过在VM选项中添加'-XX:LoopOptsCount = 0',您的原始程序(没有printf)正常退出。

将“volatile”关键字添加到字段声明也将停止转换,请参阅When Java refresh Thread Cache to actual copy

© www.soinside.com 2019 - 2024. All rights reserved.