在 volatile 变量写入后写入的变量的可见性

问题描述

public class Test {
    private static volatile boolean flag = false;
    private static int i = 1;
    public static void main(String[] args) {
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printstacktrace();
            }
            flag = true;
            i += 1;
        }).start();
        new Thread(() -> {
            while (!flag) {
                if (i != 1) {
                    System.out.println(i);
                }
            }
            System.out.println(flag);
            System.out.println(i);
        }).start();
    }
}

变量i写在volatile变量标志之后,但是代码输出true 2。看来第一个线程对i修改对第二个线程是可见的。

按照我的理解,变量I应该写在flag之前,这样第二个线程就可以知道变化了。

解决方法

根据语言标准(第 17.4 节):

一个字段可能被声明为 volatile,在这种情况下,Java 内存模型 确保所有线程看到变量的一致值

所以非正式地,所有线程都会看到该变量必须更新的值。

然而,volatile 子句不仅意味着确保目标变量的可见性保证,还意味着 full volatile visibility guarantee,即:

实际上,Java volatile 的可见性保证超出了 volatile 变量本身。可见性保证如下:

如果线程 A 写入一个 volatile 变量,然后线程 B 写入 读取相同的 volatile 变量,然后所有变量对 Thread 可见 A 在写入 volatile 变量之前,也将对 Thread 可见 B 读取 volatile 变量后。

如果线程 A 读取一个 volatile 变量,那么所有对线程 A 可见的变量,当 读取 volatile 变量也会从主内存中重新读取。

按照我的理解,变量I应该是之前写的 标志,那么第二个线程可以知道变化。

“在写入 volatile 变量之前线程 A 可见的所有变量”,它不指对这些变量进行操作

,

内存模型定义了保证,但是任何事情都可能发生在它们之上。

在 x86 上,所有写入都具有释放语义,一旦您写入一个变量,它的更新值将尽快从其他线程可见。

因此,写入 volatile 变量之前的操作发生在读取它之后的操作之前,并不会阻止写入后的操作在读取后变得可见。