Java Variable Visibility
Asked Answered
P

2

5

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

Pedestal answered 14/7, 2020 at 10:45 Comment(4)
I noticed the behaviour to change when the t1 waits also in the loop body: demo. So it probably has something to do that while(running) {} will be converted to a while(true) {} loop by the JIT compiler, because it has been deemed a hotspotJayjaycee
@Lino-Votedon'tsayThanks yeah,I have the same conjecture, the JIT may optimize the loop, but how to verify thisPedestal
@apangin do you have any advice about thisPedestal
There is no happens-before between reads and writes to running so partucular JVM implementation you are using can do whatever it wants regarding visibility if that variable in different threads. Some threads may see initial value, some may see new valueMoneyed
M
6

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.

Maine answered 14/7, 2020 at 11:14 Comment(2)
as @Lino-Votedon'tsayThanks said ,is it possible that the JIT convert while(running) {} to while(true) {}?Pedestal
@ericzhao Yes, it can. And this is one reason more to make running a volatile variable.Pulsation
B
1

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.

Bittern answered 14/7, 2020 at 11:11 Comment(2)
if I change 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(); ``` I didn't call sleep or yield and the result is the somePedestal
@ericzhao then it might have been optimised by the runtime compiler, it might have assumed while (running) is the same as while(true), which, in fact, is (given that the variable is non-volatile)Bittern

© 2022 - 2024 — McMap. All rights reserved.