我有以下代码:
public class MyApp {
public static void main(String[] args) throws InterruptedException {
SharedResource sharedResource = new SharedResource();
Runnable first = () -> {
sharedResource.increment(10000);
};
Runnable second = () -> {
sharedResource.increment(10000);
};
Thread thread = new Thread(first, "FirstThread");
Thread thread2 = new Thread(second, "SecondThread");
thread.start();
thread2.start();
thread.join();
thread2.join();
System.out.println("The value of counter is " + sharedResource.getCounter());
}
}
使用此类:
public class SharedResource {
private int counter;
public void increment(int times) {
for (int x=1; x<=times;x++) {
counter++;
// System.out.println(counter);
}
}
public void decrement() {
counter--;
}
public int getCounter() {
return counter;
}
}
我很好奇为什么这种情况一直发生。
从增量方法中删除System.out.println()
时,总值
System.out.println("The value of counter is " + sharedResource.getCounter());
是随机的 - 这是例外,因为多个线程共享同一个counter
。
但是,当增量方法上显示System.out.println(counter);
时,代码似乎不再具有多线程问题。
计数器的最终结果始终是 20,000,这不包括代码从每个线程中倾斜 10,000 次。谁能解释一下为什么会这样?
这是由于比赛窗口非常小。
默认系统输出是线程安全的 PrintStream:
public void println(int x) {
synchronized (this) {
print(x);
newLine();
}
}
所以基本上线程执行以下操作:
- 增量计数器(~数十 ns(
- 等待前一个线程释放锁,获取它并打印到控制台(~毫秒,慢 1000 倍(
- 转到 1
当你的关键部分比非关键部分长 1000 倍时,你的线程基本上是序列化的,计数器更新重叠的可能性变得非常小,系统输出中没有什么特别的。
证明方法:
您可以编写非线程安全的 PrintStream 实现:
public class NullPrintStream extends PrintStream { public NullPrintStream() { // Utility constant from apache-commons super(NullOutputStream.NULL_OUTPUT_STREAM); } @Override public void println(int x) { // No synchronization here } }
然后通过System.setOut(new NullPrintStream())
将其设置为系统,结果将再次开始拍打。
为了在开始时提供更大的比赛窗口,您可以在闩锁上同步您的可运行物,以便它们几乎同时启动:
CountDownLatch latch = new CountDownLatch(1); Runnable first = () -> { try { latch.await(); sharedResource.increment(10000); } catch (Exception e) { } }; // ... start your threads here latch.countDown();
然后,如果您运行此示例几次,您将看到类似以下内容(请注意,我将其打印到System.err
因为我已覆盖System.out
(计数器的值为 20000
计数器的值是 19996
计数器的值是 19994
计数器的值是 19999
计数器的值为 20000