与System.out关联的Java线程的奇怪行为
我有一个简单的TestThreadClientMode
类来测试竞争条件。 我尝试了两次尝试:
- 当我使用
System.out.println(count);
运行以下代码时System.out.println(count);
在第二个post中评论,输出是:
OS: Windows 8.1 flag done set true ...
而第二个线程永远活着。 因为第二个线程永远不会看到由主线程设置为true的done
标志的更改。
-
当我取消注释
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();