与System.out关联的Java线程的奇怪行为

我有一个简单的TestThreadClientMode类来测试竞争条件。 我尝试了两次尝试:

  1. 当我使用System.out.println(count);运行以下代码时System.out.println(count); 在第二个post中评论,输出是:

OS: Windows 8.1 flag done set true ...

而第二个线程永远活着。 因为第二个线程永远不会看到由主线程设置为true的done标志的更改。

  1. 当我取消注释System.out.println(count); 输出是:

    OS: Windows 8.1 0 ... 190785 190786 flag done set true Done! Thread-0 true

程序在1秒后停止。

System.out.println(count);怎么做的System.out.println(count); 让第二个线程看到done的更改?

 public class TestThreadClientMode { private static boolean done; public static void main(String[] args) throws InterruptedException { new Thread(new Runnable() { public void run() { int count = 0; while (!done) { count ++; //System.out.println(count); } System.out.println("Done! " + Thread.currentThread().getName() + " " + done); } }).start(); System.out.println("OS: " + System.getProperty("os.name")); Thread.sleep(1000); done = true; System.out.println("flag done set true "); } } 

这是内存一致性错误的一个很好的例子。 简单地说,变量已更新,但第一个线程并不总是看到变量变化。 这个问题可以通过声明它done变量volatile来解决:

 private static volatile boolean done; 

在这种情况下,对变量的更改对所有线程都是可见的,程序总是在一秒后终止。

更新:似乎使用System.out.println确实解决了内存一致性问题 – 这是因为print函数使用了实现同步的底层流。 同步建立了一个发生在之前的关系,如我链接的教程中所述,它与volatile变量具有相同的效果。 ( 这个答案的详细信息。也可以归功于@Chris K指出流操作的副作用。)

System.out.println(count)是怎么做的; 让第二个线程看到done的更改?

您正在目睹println的副作用; 你的程序正在遭遇并发竞争条件。 在协调CPU之间的数据时,告诉Java程序要在CPU之间共享数据是很重要的,否则CPU可以自由地相互延迟通信。

在Java中有几种方法可以做到这一点。 主要的两个是关键字’volatile’和’synchronized’,它们都将硬件人称之为“内存障碍”的内容插入到代码中。 如果不在代码中插入“内存障碍”,则不会定义并发程序的行为。 也就是说,我们不知道“完成”何时会被其他CPU看到,因此它就是竞争条件。

这是System.out.println的实现; 注意使用synchronized。 synchronized关键字负责在生成的汇编程序中放置内存屏障,这会产生使变量“完成”对其他CPU可见的副作用。

 public void println(boolean x) { synchronized (this) { print(x); newLine(); } } 

对程序的正确修复是在读取完成时放置读取内存屏障,在写入时写入内存屏障。 通常,这是通过从同步块内读取或写入“完成”来完成的。 在这种情况下,将变量标记为volatile将具有相同的净效果。 您还可以使用AtomicBoolean而不是boolean作为变量。

println()实现包含显式内存屏障:

 public void println(String x) { synchronized (this) { print(x); newLine(); } } 

这导致调用线程刷新所有变量。

以下代码将与您的行为具有相同的行为:

  public void run() { int count = 0; while (!done) { count++; synchronized (this) { } } System.out.println("Done! " + Thread.currentThread().getName() + " " + done); } 

实际上任何对象都可以用于监视器,以下也可以工作:

 synchronized ("".intern()) { } 

创建显式内存屏障的另一种方法是使用volatile ,因此以下方法将起作用:

 new Thread() { private volatile int explicitMemoryBarrier; public void run() { int count = 0; while (!done) { count++; explicitMemoryBarrier = 0; } System.out.println("Done! " + Thread.currentThread().getName() + " " + done); } }.start();