Java内存模型同步:如何引发数据可见性错误?

Java并发实践”给出了一个不安全类的示例,由于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;
    }
}

解决方法

你所遇到的问题是你不能等待足够长的时间来优化代码并且要缓存的值.

当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;
                }
            }
        }
    }
}

打印出以下显示其翻转值,但被卡住.

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

如果我添加-XX:PrintCompilation这个开关发生在你看到的时间

1705    1 % RequiresVolatileMain$MyRunnable::run @ -2 (129 bytes)   made not entrant
1705    2 % RequiresVolatileMain$MyRunnable::run @ 4 (129 bytes)

这表明代码已被编译为native是一种不是线程安全的方式.

如果你使价值波动,你会看到它无休止地翻转价值(或直到我无聊)

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

没有volatile这个失败,每个线程只看到它自己的值,所以他们都改变值,线程0看到true,线程1对于同一个字段看到false.

相关文章

最近看了一下学习资料,感觉进制转换其实还是挺有意思的,尤...
/*HashSet 基本操作 * --set:元素是无序的,存入和取出顺序不...
/*list 基本操作 * * List a=new List(); * 增 * a.add(inde...
/* * 内部类 * */ 1 class OutClass{ 2 //定义外部类的成员变...
集合的操作Iterator、Collection、Set和HashSet关系Iterator...
接口中常量的修饰关键字:public,static,final(常量)函数...