Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java Variable Visibility

There is my code

public class Test {
    private static boolean running = true;

    public static void main(String[] args) {
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "get running:" + running);
            while (running) {
            }
            System.out.println(Thread.currentThread().getName() + "end");
        }, "t1").start();

        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            running = false;
            System.out.println(Thread.currentThread().getName() + "change running to:" + running);
        }, "t2").start();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "get running:" + running);
            try {
                TimeUnit.SECONDS.sleep(3);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + "get running:" + running);
        }, "t3").start();
    }
}

console output

t1get running:true
t3get running:true
t2change running to:false
t3get running:false

so the thread t1 is stuck in while loop. And I know if I change running to private static volatile boolean running can fix this.

I question is t3 and t2 are different thread to, why t3 can get the new value of running but t1 can't

EDIT1

@andrew-tobilko said it may because I call Thread.sleep in the loop body, so I change the code of t3

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "get running:" + running);
            long start = System.nanoTime();
            long now = System.nanoTime();
            while ((now-start)<3*1000L*1000L*1000L){
                now = System.nanoTime();
            }
            System.out.println(Thread.currentThread().getName() + "get running:" + running);
        }, "t3").start();

the result still the same

like image 920
eric zhao Avatar asked Jul 14 '20 10:07

eric zhao


2 Answers

When you have a variable which you want to use to communicate between threads, then mark that variable as volatile:

private static volatile boolean running = true;

The reason is that threads are allowed to cache variables thread-locally for performance reasons or do other performance optimizations which only work on the assumption that the value of a variable won't change in a specific section of code. But the Java compiler and runtime won't be aware that another thread might change that variable.

Adding the volatile modifier to a variable prevents those optimization and forces the optimizer to assume that this variable can change at any time for any reason.

like image 142
Philipp Avatar answered Oct 08 '22 04:10

Philipp


It seems like TimeUnit.SECONDS.sleep(3); in the third thread helps to reload running rather than taking it from the cache in registers.

Note that the compiler doesn't have to do so.

In particular, the compiler does not have to flush writes cached in registers out to shared memory before a call to Thread.sleep or Thread.yield, nor does the compiler have to reload values cached in registers after a call to Thread.sleep or Thread.yield.

https://docs.oracle.com/javase/specs/jls/se13/html/jls-17.html#jls-17.3

So it's up to the compiler (and to the optimisations it might do).

For example, in the following (broken) code fragment, assume that this.done is a non-volatile boolean field:

while (!this.done)
    Thread.sleep(1000);

The compiler is free to read the field this.done just once, and reuse the cached value in each execution of the loop. This would mean that the loop would never terminate, even if another thread changed the value of this.done.

If you put a sleep statement inside the loop in the first thread, it might also get a new value of running. Again, no guarantee.

like image 25
Andrew Tobilko Avatar answered Oct 08 '22 04:10

Andrew Tobilko