简体   繁体   中英

Unexpected thread behavior. Visibility

I have the following code:

public static boolean turn = true;

public static void main(String[] args) {
    Runnable r1 = new Runnable() {
        public void run() {
            while (true) {
                while (turn) {
                    System.out.print("a");
                    turn = false;
                }
            }
        }
    };
    Runnable r2 = new Runnable() {
        public void run() {
            while (true) {
                while (!turn) {
                    System.out.print("b");
                    turn = true;
                }
            }
        }
    };
    Thread t1 = new Thread(r1);
    Thread t2 = new Thread(r2);
    t1.start();
    t2.start();
}

In class we've learned about "Visibility" problems that may occur when using un-synchronized code. I understand that in order to save time, the compiler will decide the grab turn to the cache in the CPU for the loop, meaning that the thread will not be aware if the turn value was changed in the RAM because he doesn't check it.

From what I understand, I would expected the code to run like this:

T1 will see turn as true -> enter loop and print -> change turn to false -> gets stuck

T2 will think turn hasn't changed -> will get stuck

I would expect that if T1 will start before T2: only 'a' will be printed and both threads will run in an infinite loop without printing anything else

However, when I'm running the code sometimes I get a few "ababa...." before both threads will stuck.

What am I missing ?

EDIT:

The following code does what I expect it: the thread will run in a infinite loop:

public class Test extends Thread {
boolean keepRunning = true;

public void run() {
    long count = 0;
    while (keepRunning) {
        count++;
    }
    System.out.println("Thread terminated." + count);
}

public static void main(String[] args) throws InterruptedException {
    Test t = new Test();
    t.start();
    Thread.sleep(1000);
    t.keepRunning = false;
    System.out.println("keepRunning set to false.");
}

}

How are they different from each other ?

When I run the code, sometimes I get a few "ababa...." before both threads will stuck.

I suspect that what is happening is that the behavior is changing when the code is JIT compiled. Before JIT compilation the writes are visible because the interpreter is doing write-throughs. After JIT compilation, either the cache flushes or the reads have been optimized away ... because the memory model allows this.

What am I missing ?

What you are missing is that you are expecting unspecified behavior to be consistent. It doesn't have to be. After all, it is unspecified! (This is true, even if my proposed explanation above is incorrect.)

The fact that turn isn't volatile doesn't mean that your code WILL break, just that it MIGHT break. For all we know, the thread could see false or true at any given moment. Caches could just be randomly flushed for no reason in particular, the thread could retain its cache, etc etc.

It could be because your code is experiencing side effects from System.out.print , which internally writes to a synchronized method:

  521       private void write(String s) {
  522           try {
  523               synchronized (this) {

( Source - DocJar )

The memory effects of synchronized could be flushing the cache and therefore impact your code.

As @Stephen C said, it could also be the JIT, which might hoist the boolean check because it assumes that the value can't change due to another thread.

So out of the three different possibilities mentioned so far, they could all be factors to contribute to how your code behaves. Visibility is a factor, not a determiner.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM