java volatile数组,我的测试结果与预期不符
java volatile array,My test results do not match the expectations
根据这个问题的答案(Java volatile array?),我做了如下测试:
public class Test {
public static volatile long[] arr = new long[20];
public static void main(String[] args) throws Exception {
new Thread(new Thread(){
@Override
public void run() {
//Thread A
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
arr[19] = 2;
}
}).start();
new Thread(new Thread(){
@Override
public void run() {
//Thread B
while (arr[19] != 2) {
}
System.out.println("Jump out of the loop!");
}
}).start();
}
}
据我所知,对于数组对象,volatile
关键字只保证arr引用的可见性,不保证数组中的元素。
但是当线程A改变了arr[19],线程B发现了arr[19]的改变并跳出循环。
那么问题是什么?
在多线程中有两件事需要考虑。
- 复合语句原子性(执行复合语句需要锁)
- 可见性(值的线程间可见性)
你的情况属于后一种。线程在它们自己的 space 中维护值的副本。其他线程 may/may 看不到一个线程在其自己的副本中修改的值。因此您可能无法复制它。
让我从修改您的示例开始:
public class Test {
public static long[] arr = new long[20]; // Make this non-volatile now
public static volatile int vol = 0; // Make another volatile variable
public static void main(String[] args) throws Exception {
new Thread(new Thread(){
@Override
public void run() {
//Thread A
try {
TimeUnit.MILLISECONDS.sleep(1000);
arr[19] = 2;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Thread(){
@Override
public void run() {
//Thread B
while (true) {
int i = vol;
if (arr[19] == 2) {
break;
}
}
System.out.println("Jump out of the loop!");
}
}).start();
}
}
你会意识到这也会导致线程 B 跳出循环(除非这种症状是 JIT 特有的,而我恰好这样做)。神奇之处在于 int i = vol;
- 或者更准确地说,是 volatile
变量的读取。删除该行将导致线程 B 无限停留在循环中。
因此,似乎对 volatile
变量的任何读取(即任何易失性读取)似乎都检索到最新的值(包括其他非易失性值)。
我试图研究 JLS,但它太复杂了,我无法完全理解。我看到一篇描述可见性保证的文章here。
来自文章:
If Thread A reads a volatile
variable, then all all variables visible
to Thread A when reading the volatile
variable will also be re-read
from main memory.
(忽略文章中的"all all"错别字。)
在这种情况下,似乎当线程读取volatile
变量时,主内存中的所有数据都会更新回CPU缓存。
其他有趣的发现:如果您向线程 B 添加另一个 Thread.sleep()
(例如休眠 50 毫秒),即使没有读取 volatile
变量,循环也会设法退出。令人惊讶的 JLS 17.3 指出:
It is important to note that neither Thread.sleep nor Thread.yield
have any synchronization semantics. In particular, the compiler does
not have to flush writes cached in registers out to shared memory
before a call to Thread.sleep or Thread.yield, nor does the compiler
have to reload values cached in registers after a call to Thread.sleep
or Thread.yield.
同样,我不确定此症状是 JIT 还是 JRE 特有的。
根据这个问题的答案(Java volatile array?),我做了如下测试:
public class Test {
public static volatile long[] arr = new long[20];
public static void main(String[] args) throws Exception {
new Thread(new Thread(){
@Override
public void run() {
//Thread A
try {
TimeUnit.MILLISECONDS.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
arr[19] = 2;
}
}).start();
new Thread(new Thread(){
@Override
public void run() {
//Thread B
while (arr[19] != 2) {
}
System.out.println("Jump out of the loop!");
}
}).start();
}
}
据我所知,对于数组对象,volatile
关键字只保证arr引用的可见性,不保证数组中的元素。
但是当线程A改变了arr[19],线程B发现了arr[19]的改变并跳出循环。
那么问题是什么?
在多线程中有两件事需要考虑。
- 复合语句原子性(执行复合语句需要锁)
- 可见性(值的线程间可见性)
你的情况属于后一种。线程在它们自己的 space 中维护值的副本。其他线程 may/may 看不到一个线程在其自己的副本中修改的值。因此您可能无法复制它。
让我从修改您的示例开始:
public class Test {
public static long[] arr = new long[20]; // Make this non-volatile now
public static volatile int vol = 0; // Make another volatile variable
public static void main(String[] args) throws Exception {
new Thread(new Thread(){
@Override
public void run() {
//Thread A
try {
TimeUnit.MILLISECONDS.sleep(1000);
arr[19] = 2;
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
new Thread(new Thread(){
@Override
public void run() {
//Thread B
while (true) {
int i = vol;
if (arr[19] == 2) {
break;
}
}
System.out.println("Jump out of the loop!");
}
}).start();
}
}
你会意识到这也会导致线程 B 跳出循环(除非这种症状是 JIT 特有的,而我恰好这样做)。神奇之处在于 int i = vol;
- 或者更准确地说,是 volatile
变量的读取。删除该行将导致线程 B 无限停留在循环中。
因此,似乎对 volatile
变量的任何读取(即任何易失性读取)似乎都检索到最新的值(包括其他非易失性值)。
我试图研究 JLS,但它太复杂了,我无法完全理解。我看到一篇描述可见性保证的文章here。
来自文章:
If Thread A reads a
volatile
variable, then all all variables visible to Thread A when reading thevolatile
variable will also be re-read from main memory.
(忽略文章中的"all all"错别字。)
在这种情况下,似乎当线程读取volatile
变量时,主内存中的所有数据都会更新回CPU缓存。
其他有趣的发现:如果您向线程 B 添加另一个 Thread.sleep()
(例如休眠 50 毫秒),即使没有读取 volatile
变量,循环也会设法退出。令人惊讶的 JLS 17.3 指出:
It is important to note that neither Thread.sleep nor Thread.yield have any synchronization semantics. In particular, the compiler does not have to flush writes cached in registers out to shared memory before a call to Thread.sleep or Thread.yield, nor does the compiler have to reload values cached in registers after a call to Thread.sleep or Thread.yield.
同样,我不确定此症状是 JIT 还是 JRE 特有的。