当混合了易失性和非易失性字段时,易失性发生在关系之前
Volatile happens-before relationship when there's mix of volatile and non-volatile fields
我试图了解混合使用易失性和非易失性字段时易失性字段的先行行为。
假设有 1 个 WriteThread 和 5 个 ReadThread,它们 update/read SharedObject。
ReadThreads 从头开始调用方法 waitToBeStopped()
,WriteThread 在 1 秒后调用方法 stop()
。
public class SharedObject {
volatile boolean stopRequested = false;
int a = 0, b = 0, c = 0;
int d = 0, e = 0, f = 0;
// WriteThread calls this method
public void stop() {
a = 1;
b = 2;
c = 3;
stopRequested = true;
a = 4;
b = 5;
c = 6;
d = 7;
e = 8;
f = 9;
}
// ReadThread calls this method
public void waitToBeStopped() throws Exception {
while(!stopRequested) {
}
System.out.println("Stopped now.");
System.out.println(a + " " + b + " " + c + " " + d + " " + e + " " + f);
}
}
当这个程序结束时,输出是这样的。即使我尝试了 100+ 个 ReadThreads,结果也总是一样。
Stopped now.
Stopped now.
Stopped now.
Stopped now.
Stopped now.
4 5 6 7 8 9
4 5 6 7 8 9
4 5 6 7 8 9
4 5 6 7 8 9
4 5 6 7 8 9
Q1。有人可以解释为什么这总是 returns 4,5,6,7,8,9 而不是 1,2,3,0,0,0 吗?
我对 happens-before 关系的理解是这样的:
- WriteThread 写入
a=1,b=2,c=3
发生在 WriteThread 写入 stopRequested
之前
- WriteThread 写入
stopRequested
发生在 WriteThread 写入 a=4,b=5,c=6,d=7,e=8,f=9
之前
- WriteThread 写入
stopRequested
发生在 ReadThread 读取 stopRequested
之前
- ReadThread 读取
stopRequested
发生在 ReadThread 读取 a,b,c,d,e,f
之前
从这 4 个陈述中,我无法得出这样的一个...
- WriteThread 写入
a=4,b=5,c=6,d=7,e=8,f=9
发生在 ReadThread 读取 a,b,c,d,e,f
之前
下面是代码的另一部分,如果有帮助的话:
public class App {
public static void main(String[] args) throws Exception {
SharedObject sharedObject = new SharedObject();
for(int i =0 ; i < 5; i++) {
Runnable rThread = new ReadThread(sharedObject);
new Thread(rThread).start();
}
Runnable wThread = new WriteThread(sharedObject);
new Thread(wThread).start();
}
}
public class WriteThread implements Runnable {
private SharedObject sharedObject;
public WriteThread(SharedObject sharedObject) {
this.sharedObject = sharedObject;
}
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
sharedObject.stop();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class ReadThread implements Runnable {
private SharedObject sharedObject;
public ReadThread(SharedObject sharedObject) {
this.sharedObject = sharedObject;
}
public void run() {
try {
sharedObject.waitToBeStopped();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
您关于 stopRequested = true;
之后的写入不保证对读者可见的假设是正确的。作者不保证将这些写入共享 cache/memory ,在那里它们对读者可见。它可以只将它们写入本地缓存,读者将看不到更新后的值。
Java 语言保证 可见性,例如当您使用 volatile 变量时。但是不保证非-volatile变量不会可见其他线程。在您的情况下,此类写入仍然可以像此处一样可见。 JVM 实现、处理器的内存一致性模型和其他方面影响可见性。
请注意,JLS 和 happens-before 关系是一种规范。 JVM 实现和硬件通常做的比 JLS 指定的更多,这可能导致 JLS 不必可见的写入的可见性。
我试图了解混合使用易失性和非易失性字段时易失性字段的先行行为。
假设有 1 个 WriteThread 和 5 个 ReadThread,它们 update/read SharedObject。
ReadThreads 从头开始调用方法 waitToBeStopped()
,WriteThread 在 1 秒后调用方法 stop()
。
public class SharedObject {
volatile boolean stopRequested = false;
int a = 0, b = 0, c = 0;
int d = 0, e = 0, f = 0;
// WriteThread calls this method
public void stop() {
a = 1;
b = 2;
c = 3;
stopRequested = true;
a = 4;
b = 5;
c = 6;
d = 7;
e = 8;
f = 9;
}
// ReadThread calls this method
public void waitToBeStopped() throws Exception {
while(!stopRequested) {
}
System.out.println("Stopped now.");
System.out.println(a + " " + b + " " + c + " " + d + " " + e + " " + f);
}
}
当这个程序结束时,输出是这样的。即使我尝试了 100+ 个 ReadThreads,结果也总是一样。
Stopped now.
Stopped now.
Stopped now.
Stopped now.
Stopped now.
4 5 6 7 8 9
4 5 6 7 8 9
4 5 6 7 8 9
4 5 6 7 8 9
4 5 6 7 8 9
Q1。有人可以解释为什么这总是 returns 4,5,6,7,8,9 而不是 1,2,3,0,0,0 吗?
我对 happens-before 关系的理解是这样的:
- WriteThread 写入
a=1,b=2,c=3
发生在 WriteThread 写入stopRequested
之前
- WriteThread 写入
stopRequested
发生在 WriteThread 写入a=4,b=5,c=6,d=7,e=8,f=9
之前
- WriteThread 写入
stopRequested
发生在 ReadThread 读取stopRequested
之前
- ReadThread 读取
stopRequested
发生在 ReadThread 读取a,b,c,d,e,f
之前
从这 4 个陈述中,我无法得出这样的一个...
- WriteThread 写入
a=4,b=5,c=6,d=7,e=8,f=9
发生在 ReadThread 读取a,b,c,d,e,f
之前
下面是代码的另一部分,如果有帮助的话:
public class App {
public static void main(String[] args) throws Exception {
SharedObject sharedObject = new SharedObject();
for(int i =0 ; i < 5; i++) {
Runnable rThread = new ReadThread(sharedObject);
new Thread(rThread).start();
}
Runnable wThread = new WriteThread(sharedObject);
new Thread(wThread).start();
}
}
public class WriteThread implements Runnable {
private SharedObject sharedObject;
public WriteThread(SharedObject sharedObject) {
this.sharedObject = sharedObject;
}
public void run() {
try {
TimeUnit.SECONDS.sleep(1);
sharedObject.stop();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public class ReadThread implements Runnable {
private SharedObject sharedObject;
public ReadThread(SharedObject sharedObject) {
this.sharedObject = sharedObject;
}
public void run() {
try {
sharedObject.waitToBeStopped();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
您关于 stopRequested = true;
之后的写入不保证对读者可见的假设是正确的。作者不保证将这些写入共享 cache/memory ,在那里它们对读者可见。它可以只将它们写入本地缓存,读者将看不到更新后的值。
Java 语言保证 可见性,例如当您使用 volatile 变量时。但是不保证非-volatile变量不会可见其他线程。在您的情况下,此类写入仍然可以像此处一样可见。 JVM 实现、处理器的内存一致性模型和其他方面影响可见性。
请注意,JLS 和 happens-before 关系是一种规范。 JVM 实现和硬件通常做的比 JLS 指定的更多,这可能导致 JLS 不必可见的写入的可见性。