当System.out.println显示在关键部分中时,多个线程显示例外值



我有以下代码:

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();
}
}

所以基本上线程执行以下操作:

  1. 增量计数器(~数十 ns(
  2. 等待前一个线程释放锁,获取它并打印到控制台(~毫秒,慢 1000 倍(
  3. 转到 1

当你的关键部分比非关键部分长 1000 倍时,你的线程基本上是序列化的,计数器更新重叠的可能性变得非常小,系统输出中没有什么特别的。

证明方法:

  1. 您可以编写非线程安全的 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())将其设置为系统,结果将再次开始拍打。

  1. 为了在开始时提供更大的比赛窗口,您可以在闩锁上同步您的可运行物,以便它们几乎同时启动:

    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

最新更新