为什么这个java程序挂起?

为什么这个java程序挂起?,java,Java,以下程序挂起,但如果我取消对System.out.printf的注释,它将正常退出。我想知道为什么会这样。提前谢谢 public class MainClass{ public static class Node{ public Integer val; Node() { val = new Integer(0); } public void hang() { int i

以下程序挂起,但如果我取消对
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版本的输出

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)

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

有关volatile的更多信息,请参见:


正如您所问的,为什么会发生这种情况:多线程是一种你应该假设发生的事情是随机的东西。我的猜测是printf处的线程在等待某个IO资源,等待时它将挂起,但当它恢复时,缓存将首先刷新。但正如我刚才所说,这只是一个猜测,因为您永远无法确定在执行未同步的线程操作时,解决方案如maslan的回答所示。但是,对于您的问题,我认为添加'System.out.printf'将停止挂起的原因是,如果没有,JIT编译器将转换代码,如下所示:

if (this.val != 1) while(true) {i++;}
我猜添加调用将停止JIT进行优化。因此,当线程决定从内存中读取而不使用缓存时,您的程序会正常退出

我猜测的一个证据是,通过向VM选项添加'-XX:loopOptCount=0',您的原始程序(不带printf)将正常退出


在字段声明中添加'volatile'关键字也会停止转换,请参见

也许您是对的。但是当我们使用
System.out.printf(“”)时,为什么它不挂起在while循环中?@NicholasK它只是一个缓存,你不知道何时以及如何刷新它。这就像使用BufferedWriter而不在最后关闭writer一样:您完全不知道缓冲区中还有多少文本,最后有多少文本将最终进入文件,以及缓冲区中还有多少文本。(显然,如果您使用Java7并尝试使用资源,您将永远看不到这一点)。可能写入控制台的额外时间导致缓存刷新,可能线程在该操作期间挂起,等待IO资源,当重新启动以继续时,缓存会提前刷新
printf
中断循环,因为它在后台使用同步,为了防止对同一个内部文件描述符的两次调用(还有什么会产生未定义的行为)@Ferrybig我仍然确信这只是一个猜测,但感谢你让我相信我的想法:)我想指出,我们都只是猜测,所以没有人会把我们的两个答案都当作100%肯定的解释,多线程婴儿;)是,通过添加
-XX:LoopOptCount=0
程序将正常退出。但是有没有办法直接证明你的猜测?我使用hsdis来查看汇编代码,但没有看到您上面提到的这种结构。@Charles添加
-XX:+PrintOptoAssembly
来显示优化的汇编如何?@CGBCBC还没有调试构建jvm。我稍后再试试。