问题描述
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 变量之前的操作发生在读取它之后的操作之前,并不会阻止写入后的操作在读取后变得可见。