Strange behavior of a Java thread associated with System.out [duplicate]

会有一股神秘感。 提交于 2020-01-01 05:39:27

问题


I have a simple TestThreadClientMode class to test a race condition. I tried two attempts:

  1. When I run the following code with System.out.println(count); commented in the second thread, the output was:

OS: Windows 8.1 flag done set true ...

and the second thread was alive forever. Because the second thread never sees change of the done flag which was set true by Main thread.

  1. When I uncommented System.out.println(count); the output was:

    OS: Windows 8.1 0 ... 190785 190786 flag done set true Done! Thread-0 true

And the program stopped after 1 second.

How did System.out.println(count); make the second thread see the change in done?

Code

public class TestThreadClientMode {
    private static boolean done;
    public static void main(String[] args) throws InterruptedException {
        new Thread(new Runnable() {
            public void run() {
                int count = 0;
                while (!done) {
                    count ++;
                    //System.out.println(count);
                }
                System.out.println("Done! " + Thread.currentThread().getName() + "  " + done);
            }
        }).start();
        System.out.println("OS: " + System.getProperty("os.name"));

        Thread.sleep(1000);
        done = true;

        System.out.println("flag done set true ");
    }
}

回答1:


This is a brilliant example of memory consistency errors. Simply put, the variable is updated but the first thread does not always see the variable change. This issue can be solved by making done variable volatile by declaring it like so:

private static volatile boolean done;

In this case, changes to the variable are visible to all threads and the program always terminates after one second.

Update: It appears that using System.out.println does indeed solve the memory consistency issue - this is because the print function makes use of an underlying stream, which implements synchronization. Synchronization establishes a happens-before relationship as described in the tutorial I linked, which has the same effect as the volatile variable. (Details from this answer. Also credit to @Chris K for pointing out the side effect of the stream operation.)




回答2:


How did System.out.println(count); make the second thread see the change in done?

You are witnessing a side effect of println; your program is suffering from a concurrent race condition. When coordinating data between CPUs it is important to tell the Java program that you want to share the data between the CPUs, otherwise the CPUs are free to delay communication with each other.

There are a few ways to do this in Java. The main two are the keywords 'volatile' and 'synchronized' which both insert what hardware guys call 'memory barriers' into your code. Without inserting 'memory barriers' into the code, the behaviour of your concurrent program is not defined. That is, we do not know when 'done' will become visible to the other CPU, and it is thus a race condition.

Here is the implementation of System.out.println; notice the use of synchronized. The synchronized keyword is responsible for placing memory barriers in the generated assembler which is having the side effect of making the variable 'done' visible to the other CPU.

public void println(boolean x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

The correct fix for your program, is to place a read memory barrier when reading done and a write memory barrier on writing to it. Typically this is done by reading or writing to 'done' from within a synchronized block. In this case, marking the variable done as volatile will have the same net effect. You can also use an AtomicBoolean instead of boolean for the variable.




回答3:


The println() implementation contains explicit memory barrier:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

Which causes the invoking thread to refresh all variables.

The following code will have the same behavior as yours:

    public void run() {
        int count = 0;
        while (!done) {
            count++;
            synchronized (this) {
            }
        }
        System.out.println("Done! " + Thread.currentThread().getName() + "  " + done);
    }

In fact any object can be used for monitor, following will also work:

synchronized ("".intern()) {
}

Another way to create explicit memory barrier is using volatile, so the following will work:

new Thread() {
    private volatile int explicitMemoryBarrier;
    public void run() {
        int count = 0;
        while (!done) {
            count++;
            explicitMemoryBarrier = 0;
        }
        System.out.println("Done! " + Thread.currentThread().getName() + "  " + done);
    }
}.start();


来源:https://stackoverflow.com/questions/32294288/strange-behavior-of-a-java-thread-associated-with-system-out

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!