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]的改变并跳出循环。

那么问题是什么?

在多线程中有两件事需要考虑。

  1. 复合语句原子性(执行复合语句需要锁)
  2. 可见性(值的线程间可见性)

你的情况属于后一种。线程在它们自己的 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 特有的。