如何演示javamultithreading可见性问题?

如果Java中的variables是从多个线程访问的,则必须确保它们已被安全地发布。 这通常意味着使用synchronizedvolatile

我有一个印象,就是有些同事没有认真对待这个问题,因为他们“从来没有听说过volatile ,而且他们的计划已经运作多年了”。

所以我的问题是:

有人可以提供一个示例Java程序/代码片断,可靠地显示数据可见性问题。

我认为运行一个程序,看到意想不到的NPE或陈旧的variables值,将会对帮助更多,而不仅仅是理论上的解释,这是无法certificate的。

非常感谢你的帮助!

更新:只是为了强调这一点。 我已经阅读过Java Concurreny的实践,并且知道理论上存在可见性问题的例子。 我正在寻找的是一种真正展示他们的方法。 我不确定,这实际上是可能的,但也许有一个jvmconfiguration或类似的东西,允许它。

通过删除操作在这里修改示例,我想出了一个在我的环境中始终失败的示例(线程永不停止运行)。

 // Java environment: // java version "1.6.0_0" // OpenJDK Runtime Environment (IcedTea6 1.6.1) (6b16-1.6.1-3ubuntu3) // OpenJDK 64-Bit Server VM (build 14.0-b16, mixed mode) public class Test2 extends Thread { boolean keepRunning = true; public static void main(String[] args) throws InterruptedException { Test2 t = new Test2(); t.start(); Thread.sleep(1000); t.keepRunning = false; System.out.println(System.currentTimeMillis() + ": keepRunning is false"); } public void run() { while (keepRunning) {} } } 

请注意,这种types的问题完全取决于编译器/运行时/系统。 特别是编译器可以决定添加指令从内存中读取variables,即使它不是易失性的 – 所以代码将起作用,vm和jit可以优化从内存中读取的内容并仅使用寄存器,甚至处理器可以对指令进行重新sorting – 不会影响这种情况,但是在其他multithreading情况下,如果修改了多个variables,则会影响其他线程的感知状态。

有人可以提供一个示例Java程序/代码片断,可靠地显示数据可见性问题。

不,没有可靠的例子显示数据可见性问题。

原因是任何有效的执行一个程序 volatile也是一个有效执行相同的程序没有 volatile 。 (相反,显然不是真的!)

最常见的例子强调使用volatile的重要性是while(keepRunning)例子:

 public class Test extends Thread { boolean keepRunning = true; public static void main(String[] args) throws InterruptedException { Test t = new Test(); t.start(); Thread.sleep(1000); t.keepRunning = false; System.out.println(System.currentTimeMillis() + ": keepRunning is false"); } public void run() { while (keepRunning) System.out.println(System.currentTimeMillis() + ": " + keepRunning); } } 

由于keepRunning可能(有效地)保存在运行while循环的线程的caching中,因此keepRunning设置为false后,此程序可能会长时间输出“true”。

但是请注意没有可靠的方式来暴露种族条件。 (请参阅我的其他答案。)此示例可能会在某些情况下在硬件/操作系统/ jvm的某些组合上公开它。

让他们通过Java并发性大师Brian Goetz阅读“ 实践中的Java并发”一书。 对于任何需要用Java编写任何真正的并发软件的人来说,这本书都是必读的!

当然,说“我从来没有听说过volatile ,我的计划已经工作了好几年”,这是愚蠢的愚蠢论调 。

我有一段代码给你:

 package test; public class LoopTest { private boolean done = false; /** * @param args */ public void start() { System.out.println(System.getProperty("java.vm.name")); System.out.println(System.getProperty("java.version")); for (int i = 0; i < 100; i++) { startNewThread(); } try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } done = true; System.out.println("forcing end"); } private void startNewThread() { new Thread(new Runnable() { public void run() { long i = 0; while(!done) { i++; if(i % 100L == 0) { System.out.println("still working " + i); } } System.out.println("ending " + i); } }).start(); } public static void main(String[] args) { new LoopTest().start(); } } 

在JVM服务器模式下运行的这个例子在我的机器上产生了这个输出:

 .. .. ending 14100 still working 14800 ending 14800 still working 26500 ending 26500 still working 18200 ending 18200 still working 9400 ending 9400 still working 1300 ending 1300 still working 59500 ending 59500 still working 1700 still working 75400 ending 75400 still working 33500 ending 33500 still working 36100 ending 36100 still working 121000 ending 121000 still working 3000 ending 3000 ending 1700 still working 5900 ending 5900 still working 7800 ending 7800 still working 7800 ending 7800 still working 6800 ending 6800 still working 5300 ending 5300 still working 9100 still working 10600 ending 10600 still working 9600 ending 9600 still working 10000 ending 10000 ending 9100 still working 1700 ending 1700 .. .. 

看看“结局#”的陈述:他们都有一个数量是100的倍数,这是不太可能发生的,我想。 我的解释是有一个可见性问题,导致线程仍然读取完成==假,虽然它已经更新为true。 在使用“still working#”语句调用同步的System.out.println()方法之后,线程将读取已更新的done和exit值。

还是有人看到我的代码/解释中的错误?

 public class NoVisibility { private static boolean ready = false; private static int number; private static class ReaderThread extends Thread { @Override public void run() { while (!ready) { Thread.yield(); } System.out.println(number); } } public static void main(String[] args) throws InterruptedException { new ReaderThread().start(); number = 42; Thread.sleep(20000); ready = true; } } 

Thread.sleep()调用放置20秒会发生什么,JIT将在这20秒内启动,它将优化检查并caching值或完全删除条件。 所以代码在可见性上会失败。

要阻止这种情况发生,你必须使用volatile

对David的代码(configurationJava6 64bit,Eclipse Juno SR2)的扩展:

 public class NoVisibility_Demonstration extends Thread { boolean keepRunning = true; public static void main(String[] args) throws InterruptedException { NoVisibility_Demonstration t = new NoVisibility_Demonstration(); t.start(); Thread.sleep(1000); t.keepRunning = false; System.out.println(System.currentTimeMillis() + ": keepRunning is false"); } public void run() { int x = 10; while (keepRunning) { //System.out.println("If you uncomment this line, the code will work without the visibility issue"); x++; } System.out.println("x:"+x); } } 

使用这个例子,你可以显示两种情况。 在run()中的while循环中取消注释行时,可见性问题已解决。 原因是println()语句使用同步。 详细讨论这里