Java volatile 读取可见性保证
The Java volatile Read Visibility Guarantee
我在阅读 JVM 在读取 volatile 变量时提供的可见性保证时遇到了以下摘录:
“当线程 A 写入一个易失性变量并且随后线程 B 读取该相同变量时,在写入易失性变量之前对 A 可见的所有变量的值在读取易失性变量后对 B 可见。 "
我对 JVM 的这种保证有疑问。考虑下面的一组 类 :
public class Test {
public static void main(String[] args) throws InterruptedException {
POJO p = new POJO();
new Th1(p).start();
new Th2(p).start();
}
}
public class Th1 extends Thread {
private POJO p1 = null;
public Th1(POJO obj) {
p1 = obj;
}
@Override
public void run() {
p1.a = 10; // t = 1
p1.b = 10; // t = 2
p1.c = 10; // t = 5;
System.out.println("p1.b val: " + p1.b); // t = 8
System.out.println("Thread Th1 finished"); // t = 9
}
}
public class Th2 extends Thread {
private POJO p2 = null;
public Th2(POJO obj) {
p2 = obj;
}
@Override
public void run() {
p2.a = 30; // t = 3
p2.b = 30; // t = 4
int x = p2.c; // t = 6
System.out.println("p2.b value: " + p2.b); // t = 7
}
}
public class POJO {
int a = 1;
int b = 1;
volatile int c = 1;
}
假设 2 个线程 Th1 和 Th2 运行 在不同的 CPU 中,它们的指令执行顺序由每行中的注释指示(在它们的 运行 方法中)。我的问题是:
当代码“int x = p2.c;”在 t = 6 时执行,线程 Th2 可见的变量应该按照上述段落从主内存中刷新。据我所知,此时主内存将拥有来自 Th1 的所有写入。变量 p2.b 在 t = 7 时打印时将显示什么值?
- p2.b 是否会显示值 10,因为它的值是从易失性变量 p2.c 的读取中刷新的?
- 或者它会以某种方式保留值 30?
对于您的代码,p2.b 不能保证是 10 或 30。写入是竞争条件。
“当线程 A 写入一个 volatile 变量并且 随后 线程 B 读取同一个变量时,A 之前可见的所有变量的值在读取 volatile 变量后写入 volatile 变量对 B 可见。
Th2 读取 p2.c 不能保证在 Th1 写入 p1.c 之后完成。
对于您讨论的特定顺序,Th2 中 p2.c 的读取将不会 将 p2.b 的值恢复为 10。
a
的写入和a
的读取之间没有边缘发生。由于它们是相互冲突的操作(其中至少有一个是写操作)并且在同一地址上,因此存在 data-race,因此程序行为未定义。
我认为以下示例更好地解释了您正在寻找的行为:
public class Test {
public static void main(String[] args) throws InterruptedException {
POJO p = new POJO();
new Th1(p).start();
new Th2(p).start();
}
}
public class Th1 extends Thread {
private POJO p1 = null;
public Th1(POJO obj) {
p1 = obj;
}
@Override
public void run() {
a=1;
b=1;
}
}
public class Th2 extends Thread {
private POJO p2 = null;
public Th2(POJO obj) {
p2 = obj;
}
@Override
public void run() {
if(p.b==1)println("a must be 1, a="+p2.a);
}
}
public class POJO {
int a = 0;
volatile int b = 0;
}
a
的写入和b
的写入之间有一个happens before edge(程序顺序规则)
在 b
的写入和随后的 b
的读取之间有一个 happens before edge(volatile 变量规则)
在b
的读取和a
的读取之间有一个happens before edge(程序顺序规则)
由于happens before关系是传递性的,所以a
的写和a
的读之间有一个happens before edge。所以第二个线程应该看到第一个线程的a=1
。
我在阅读 JVM 在读取 volatile 变量时提供的可见性保证时遇到了以下摘录: “当线程 A 写入一个易失性变量并且随后线程 B 读取该相同变量时,在写入易失性变量之前对 A 可见的所有变量的值在读取易失性变量后对 B 可见。 "
我对 JVM 的这种保证有疑问。考虑下面的一组 类 :
public class Test {
public static void main(String[] args) throws InterruptedException {
POJO p = new POJO();
new Th1(p).start();
new Th2(p).start();
}
}
public class Th1 extends Thread {
private POJO p1 = null;
public Th1(POJO obj) {
p1 = obj;
}
@Override
public void run() {
p1.a = 10; // t = 1
p1.b = 10; // t = 2
p1.c = 10; // t = 5;
System.out.println("p1.b val: " + p1.b); // t = 8
System.out.println("Thread Th1 finished"); // t = 9
}
}
public class Th2 extends Thread {
private POJO p2 = null;
public Th2(POJO obj) {
p2 = obj;
}
@Override
public void run() {
p2.a = 30; // t = 3
p2.b = 30; // t = 4
int x = p2.c; // t = 6
System.out.println("p2.b value: " + p2.b); // t = 7
}
}
public class POJO {
int a = 1;
int b = 1;
volatile int c = 1;
}
假设 2 个线程 Th1 和 Th2 运行 在不同的 CPU 中,它们的指令执行顺序由每行中的注释指示(在它们的 运行 方法中)。我的问题是: 当代码“int x = p2.c;”在 t = 6 时执行,线程 Th2 可见的变量应该按照上述段落从主内存中刷新。据我所知,此时主内存将拥有来自 Th1 的所有写入。变量 p2.b 在 t = 7 时打印时将显示什么值?
- p2.b 是否会显示值 10,因为它的值是从易失性变量 p2.c 的读取中刷新的?
- 或者它会以某种方式保留值 30?
对于您的代码,p2.b 不能保证是 10 或 30。写入是竞争条件。
“当线程 A 写入一个 volatile 变量并且 随后 线程 B 读取同一个变量时,A 之前可见的所有变量的值在读取 volatile 变量后写入 volatile 变量对 B 可见。
Th2 读取 p2.c 不能保证在 Th1 写入 p1.c 之后完成。
对于您讨论的特定顺序,Th2 中 p2.c 的读取将不会 将 p2.b 的值恢复为 10。
a
的写入和a
的读取之间没有边缘发生。由于它们是相互冲突的操作(其中至少有一个是写操作)并且在同一地址上,因此存在 data-race,因此程序行为未定义。
我认为以下示例更好地解释了您正在寻找的行为:
public class Test {
public static void main(String[] args) throws InterruptedException {
POJO p = new POJO();
new Th1(p).start();
new Th2(p).start();
}
}
public class Th1 extends Thread {
private POJO p1 = null;
public Th1(POJO obj) {
p1 = obj;
}
@Override
public void run() {
a=1;
b=1;
}
}
public class Th2 extends Thread {
private POJO p2 = null;
public Th2(POJO obj) {
p2 = obj;
}
@Override
public void run() {
if(p.b==1)println("a must be 1, a="+p2.a);
}
}
public class POJO {
int a = 0;
volatile int b = 0;
}
a
的写入和b
的写入之间有一个happens before edge(程序顺序规则)
在 b
的写入和随后的 b
的读取之间有一个 happens before edge(volatile 变量规则)
在b
的读取和a
的读取之间有一个happens before edge(程序顺序规则)
由于happens before关系是传递性的,所以a
的写和a
的读之间有一个happens before edge。所以第二个线程应该看到第一个线程的a=1
。