【问题标题】:Why the volatile Happens-Before order for Instruction Reordering fails?为什么指令重新排序的 volatile Happens-Before 命令失败?
【发布时间】:2018-08-08 08:44:47
【问题描述】:

我有以下代码来测试 volatile。 bEnd 和 nCount 被定义为易失性。

nCount = 0, bEnd = false

Writer 线程将设置

nCount = 100, bEnd = true

Reader 线程读取这些病毒并打印它们。基于 Java Happens-before 顺序,在我看来,volatile 确保当 bEnd = true 时 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);
    }
}

【问题讨论】:

    标签: java multithreading volatile


    【解决方案1】:

    当 bEnd = true 时,volatile 确保 nCount = 100

    从技术上讲,是的。但是读者并没有原子地阅读它们。所以它可能会打印nCount = 0 and bEnd = true。

    这是一个例子:

    1. 读者阅读nCount 0
    2. Wirter 写道 nCount = 100
    3. Wirter 写道 bEnd = true
    4. 作家打印thread Writer bEnd = true
    5. 读者阅读bEnd true

    【讨论】:

    • 很好,准确的解释!
    • 谢谢!我忘了读者不是原子的。将 println() 更改为 bEnd 的条件测试将解决问题。 if(SharedData.bEnd) { if (SharedData.nCount != 100) { new Exception("nCount = " + SharedData.nCount).printStackTrace(); }
    【解决方案2】:

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

    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 线程看到了 volatile 变量的 Write 线程的更新,它将看到之前所做的一切,因此 100。

    【讨论】:

    • Minor:请问您使用LockSupport::park的原因是什么?我个人尽量避免使用,因为如果使用不正确,它可能会干扰隐藏在例如下面的park/unpark。 ReentrantLock 导致不太明显的错误。
    • @St.Antario 嗯我不知道ReentrantLock 的事情,可能需要调查一下。我在这个例子中使用它只是因为它不会抛出检查异常(不像Thread::sleep)
    • @St.Antario 好吧,您现在引起了我的注意,愿意提供有关此的链接/问题吗?我真的很感兴趣,因为 IIRC 我们在生产中使用它,所以可能需要更改该代码。谢谢!
    • 我可以为您提供几个月前我自己在我们的 prod-env 中尝试使用 LockSupport::park 时遇到的问题。
    • 但这是一个错误使用LockSupport::park的例子。在使用 logback 添加日志记录后,我破坏了代码。因为它使用了ReentrantLock,这干扰了我的停车尝试。
    猜你喜欢
    • 2019-06-02
    • 1970-01-01
    • 2020-05-18
    • 2013-08-20
    • 1970-01-01
    • 1970-01-01
    • 2023-03-05
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多