2

The following program hangs but if I uncomment the System.out.printf it exits normally. I wonder why this happens. Thanks in advance.

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();
    }
}

output of 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)
z.h.
  • 205
  • 3
  • 8

2 Answers2

5

I believe that is because the values might be copied to CPU caches and that means that multiple threads might se a different values for the same variable. To prevent this you can tell java to use main memory. Please change "public Integer val" to "public volatile Integer val" and observe that the program now exits.

Se more on volatile here: When to use volatile with multi threading?

As you asked why does this happen: multithreading is a stuff that you should assume what happens is kinda random. My guess is that the thread at printf waits for some IO resource, and when waiting it is suspended, but when it resumes the cache is refreshed first. But as I just said, this is just a guess, as you are never sure when doing thread operations that are not synchronized

maslan
  • 2,078
  • 16
  • 34
  • 1
    Maybe you are right. But why does it *not* hang when we use `System.out.printf("");` within the while-loop? – Nicholas K Mar 13 '19 at 06:56
  • @NicholasK its just a cache, you don't know when and how it will be refreshed. It is like using BufferedWriter and not closing the writer at the end : you have absolutely no idea how much text is left in the buffer and at the end how much of the text will end up in a file, and how much of it is left in the buffer. (obviously if you use Java7 try with resources you will never see that). Perhaps some extra time to write to console is causing the cache to refresh, maybe the thread hangs during that operation waiting for IO resource and when restarted to continue the cache is refreshed beforehand – maslan Mar 13 '19 at 06:59
  • `printf` breaks the loop because it uses synchronization in the background, to prevent the 2 calls to the same internal file descriptor ( what else would create undefined behaviour ) – Ferrybig Mar 13 '19 at 07:34
  • @Ferrybig I am still sure it is just a guess, but thanks for reassuring me about my idea :) – maslan Mar 13 '19 at 07:37
1

The solution is shown in maslan's answer. However, as for your question, I think the reason why adding 'System.out.printf' will stop the hanging is that, if not, the JIT compiler will transform the code like:

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

And I guess that adding the call will stop the JIT do that optimization. So when the thread decide to read from the memory and not using the cache, your program quited normally.

One proof for my guess is that by adding '-XX:LoopOptsCount=0' to the VM options, your original program (without the printf) quit normally.

Adding 'volatile' keyword to field declaration will also stop the transformation, see When Java refresh Thread Cache to actual copy

maslan
  • 2,078
  • 16
  • 34
cgcgbcbc
  • 539
  • 4
  • 20
  • I would like to point out that we both are just guessing, so nobody will take both our answers as a 100% sure explanation, multithreading baby ;) – maslan Mar 13 '19 at 07:45
  • yes, by adding `-XX:LoopOptsCount=0` the program quit normally. But is there a way to prove your guess directly? I used hsdis to see the assembly code but didn't see such a structure you mentioned above. – z.h. Mar 13 '19 at 08:53
  • @Charles How about adding `-XX:+PrintOptoAssembly` to show optimized assembly? – cgcgbcbc Mar 14 '19 at 01:33
  • @cgcgbcbc haven't got a debug build jvm yet. will try it later. – z.h. Mar 14 '19 at 03:50