Java内存模型同步:如何诱导数据可见性bug?

DD.*_*DD. 9 java concurrency synchronization java-memory-model

"Java Concurrency in Practice"给出了以下不安全类的示例,由于Java内存模型的性质可能最终会永久运行或打印0.

这个类试图证明的问题是这里的变量不是线程之间的"共享".因此,线程看到的值可能与另一个线程不同,因为它们不是易失性或同步的.另外由于JVM ready = true允许的语句的重新排序可能在number = 42之前设置.

对我来说,这个类总是使用JVM 1.6正常工作.有关如何让这个类执行不正确的行为(即打印0或永远运行)的任何想法?

public class NoVisibility {
    private static boolean ready;
    private static int number;

    private static class ReaderThread extends Thread {
        public void run() {
            while (!ready)
                Thread.yield();
            System.out.println(number);
        }
    }

    public static void main(String[] args) {
        new ReaderThread().start();
        number = 42;
        ready = true;
    }
}
Run Code Online (Sandbox Code Playgroud)

jta*_*orn 6

java内存模型定义了工作所需的内容和不需要的内容.不安全的多线程代码的"美"在大多数情况下(特别是在受控的开发环境中)通常都有效.只有当你使用更好的计算机进行生产并且负载增加时,JIT才能真正开始尝试使用错误.


Pet*_*rey 6

您遇到的问题是您没有等待足够长的时间来优化代码并缓存值.

当x86_64系统上的线程第一次读取值时,它将获得一个线程安全副本.它只是后来的变化,它无法看到.其他CPU可能不是这种情况.

如果您尝试这样做,您可以看到每个线程都卡在其本地值.

public class RequiresVolatileMain {
    static volatile boolean value;

    public static void main(String... args) {
        new Thread(new MyRunnable(true), "Sets true").start();
        new Thread(new MyRunnable(false), "Sets false").start();
    }

    private static class MyRunnable implements Runnable {
        private final boolean target;

        private MyRunnable(boolean target) {
            this.target = target;
        }

        @Override
        public void run() {
            int count = 0;
            boolean logged = false;
            while (true) {
                if (value != target) {
                    value = target;
                    count = 0;
                    if (!logged)
                        System.out.println(Thread.currentThread().getName() + ": reset value=" + value);
                } else if (++count % 1000000000 == 0) {
                    System.out.println(Thread.currentThread().getName() + ": value=" + value + " target=" + target);
                    logged = true;
                }
            }
        }
    }
}
Run Code Online (Sandbox Code Playgroud)

打印以下内容显示其掠过值,但卡住了.

Sets true: reset value=true
Sets false: reset value=false
...
Sets true: reset value=true
Sets false: reset value=false
Sets true: value=false target=true
Sets false: value=true target=false
....
Sets true: value=false target=true
Sets false: value=true target=false
Run Code Online (Sandbox Code Playgroud)

如果我添加-XX:+PrintCompilation此切换发生在您看到的时间

1705    1 % RequiresVolatileMain$MyRunnable::run @ -2 (129 bytes)   made not entrant
1705    2 % RequiresVolatileMain$MyRunnable::run @ 4 (129 bytes)
Run Code Online (Sandbox Code Playgroud)

这表明代码已编译为本机是一种非线程安全的方式.

如果你创造价值volatile你会看到它无休止地翻转价值(或直到我感到无聊)

编辑:这个测试的作用是什么; 当它检测到的值不是线程目标值时,它会设置该值.即.线程0设置为true和线程1设置为false 当两个线程正确共享字段时,它们会看到彼此的更改,并且值始终在true和false之间翻转.

如果没有volatile,则会失败并且每个线程只看到自己的值,因此它们都会更改值,线程0看到true并且线程1看到false相同的字段.