Java 变量可见性

eri*_*hao 5 java multithreading volatile

有我的代码

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();
    }
}
Run Code Online (Sandbox Code Playgroud)

控制台输出

t1get running:true
t3get running:true
t2change running to:false
t3get running:false
Run Code Online (Sandbox Code Playgroud)

所以线程t1卡在while循环中。而且我知道如果我更改runningprivate static volatile boolean running可以解决此问题。

我的问题是t3t2不同的线程,为什么t3可以得到新值runningt1不能

编辑1

@andrew-tobilko 说可能是因为我调用Thread.sleep了循环体,所以我更改了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();
Run Code Online (Sandbox Code Playgroud)

结果还是一样

Phi*_*ipp 6

当您有一个变量要用于在线程之间进行通信时,请将该变量标记为 volatile

private static volatile boolean running = true;
Run Code Online (Sandbox Code Playgroud)

原因是允许线程出于性能原因在线程本地缓存变量,或者进行其他性能优化,这些优化仅在假设变量的值不会在特定代码部分更改的情况下起作用。但是 Java 编译器和运行时不会意识到另一个线程可能会更改该变量。

volatile修饰符添加到变量会阻止这些优化,并强制优化器假定此变量可以因任何原因随时更改。


And*_*lko 1

似乎TimeUnit.SECONDS.sleep(3);在第三个线程中有助于重新加载running,而不是从寄存器中的缓存中获取它。

请注意,编译器不必这样做。

特别是,在调用 Thread.sleep 或 Thread.yield 之前,编译器不必将缓存在寄存器中的写入刷新到共享内存,也不必在调用 Thread.sleep 或 Thread 之后重新加载缓存在寄存器中的值。 。屈服。

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

所以这取决于编译器(以及它可能进行的优化)。

例如,在以下(损坏的)代码片段中,假设 this.done 是非易失性布尔字段:

while (!this.done)
    Thread.sleep(1000);
Run Code Online (Sandbox Code Playgroud)

编译器可以自由地读取 this.done 字段一次,并在每次循环执行中重用缓存的值。这意味着循环永远不会终止,即使另一个线程更改了 this.done 的值。

如果您将一条sleep语句放入第一个线程的循环内,它也可能会获得新值running。再次强调,不能保证。