“
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.