我有以下代码:
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次,因此除外。任何人都可以解释我为什么会这样吗?
答案 0 :(得分:5)
这是由于比赛窗口非常小。
默认系统输出是PrintStream,它是线程安全的:
public void println(int x) {
synchronized (this) {
print(x);
newLine();
}
}
所以基本上线程执行以下操作:
当你的关键部分比非关键部分长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())
将其设置为系统输出,结果将再次开始拍打。
要在开头提供更大的竞赛窗口,您可以在latch上同步runnables,使它们几乎同时启动:
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