为什么指令重排序的 volatile Happens-Before 指令失败?

Why the volatile Happens-Before order for Instruction Reordering fails?

我有以下代码来测试 volatilebEndnCount 定义为可变的。

nCount = 0, bEnd = false

Writer 线程将设置

nCount = 100, bEnd = true

Reader 线程读取这些变量并打印它们。基于 Java Happens-before 顺序,在我看来,当 bEnd = true 时,volatile 确保 nCount = 100。但有时程序会打印这个:

main thread done.
thread Reader running ...
thread Writer running ...
SharedData nCount = 0, bEnd = false
thread Writer bEnd = true
thread Reader nCount = 0, bEnd = true
thread Reader nCount = 100, bEnd = true
thread Reader nCount = 100, bEnd = true
thread Reader done.

Reader如何得到"nCount = 0, bEnd = true"???

下面代码运行上windows10,jdk1.8.0_131

public class HappensBeforeWithVolatile {

    public static void main(String[] args) {

        Thread threadWriter = new Thread(new Writer());
        Thread threadReader = new Thread(new Reader());
        threadWriter.start();
        threadReader.start();

        System.out.println("main thread done.");
    }
}

class Writer implements Runnable {

    @Override
    public void run() {
        System.out.println("thread Writer running ...");
        SharedData.nCount = 100;
//        System.out.println("thread Writer nCount = 100");
        SharedData.bEnd = true;
        System.out.println("thread Writer bEnd = true");
    }
}

class Reader implements Runnable {

    @Override
    public void run() {
        System.out.println("thread Reader running ...");
        System.out.println("thread Reader nCount = " + SharedData.nCount + ", bEnd = " + SharedData.bEnd);
        System.out.println("thread Reader nCount = " + SharedData.nCount + ", bEnd = " + SharedData.bEnd);
        if (SharedData.nCount == 0 && SharedData.bEnd) {
            System.out.println("thread Reader CODE REORDER !!!");
        }
        System.out.println("thread Reader nCount = " + SharedData.nCount + ", bEnd = " + SharedData.bEnd);
        System.out.println("thread Reader done.");
    }
}

class SharedData {
    volatile public static boolean bEnd = false;
    volatile public static int nCount = 0;

    static {
        System.out.println("SharedData nCount = " + nCount + ", bEnd = " + bEnd);
    }
}

volatile 确保当 bEnd = true 时 nCount = 100

从技术上讲,是的。但是 reader 并没有自动读取它们。所以它可能会打印 nCount = 0 and bEnd = true.

这是一个例子:

  1. Reader 读取 nCount 0
  2. 维特写道 nCount = 100
  3. 维特写道 bEnd = true
  4. 作者打印 thread Writer bEnd = true
  5. Reader 读取 bEnd true

顺便说一句,您的整个示例都有点缺陷。要测试之前发生的情况,您需要测试后续操作,即使 bEnd 易变且 nCount 不是易变的,然后将您的示例简化为:

static class Reader implements Runnable {
    @Override
    public void run() {

        while (true) {
            if (SharedData.bEnd) {
                System.out.println(SharedData.nCount);
                break;
            } else {
                System.out.println("Not yet seen as true");
                LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(100));
            }
        }
    }
}

static class Writer implements Runnable {
    @Override
    public void run() {
        LockSupport.parkNanos(TimeUnit.MILLISECONDS.toNanos(1000));
        SharedData.nCount = 100;
        SharedData.bEnd = true;

    }
}

这将始终输出 100(至少在这种情况下)。正确的解释是,如果 Reader 线程看到 Write 线程对 volatile 变量的更新,它将看到之前完成的所有操作,因此 100.