5

为了通过 Java 学习同步,我只是在搞一些简单的事情,比如创建线程之间共享的计数器。

我遇到的问题是我无法弄清楚如何在 100% 的时间内按顺序打印计数器。

int counterValue = this.counter.incrementAndGet();
System.out.println(this.threadName + ": " + counterValue);

上面增加AtomicInteger counter,获取新值,并将其打印到由负责该更新的线程名称标识的控制台。当该incrementAndGet()方法似乎导致 JVM 在打印当前线程的更新值之前上下文切换到另一个线程进行更新时,就会出现问题。这意味着在线程返回执行状态之前,该值会递增但不会打印。查看此示例输出时,这一点很明显:

Thread 3: 4034
Thread 3: 4035
Thread 3: 4036
Thread 1: 3944
Thread 1: 4037
Thread 1: 4039
Thread 1: 4040
Thread 2: 3863
Thread 1: 4041
Thread 1: 4043

您可以看到,当执行返回到线程 1 时,它会打印其值并继续更新。线程 2 也是如此。

我有一种感觉,我错过了一些非常明显的东西。

4

3 回答 3

7

当出现该问题时,incrementAndGet() 方法导致 JVM 在打印当前线程的更新值之前上下文切换到另一个线程进行更新

这是在这些情况下经常发生的竞争条件。尽管AtomicInteger计数器正在正确递增,但在递增发生之后和调用之前,没有什么可以阻止Thread 2被换出的。println

int counterValue = this.counter.incrementAndGet();
// there is nothing stopping a context switch here
System.out.println(this.threadName + ": " + counterValue);

如果你想打印“100% 的时间顺序计数器”,你将不得不在增量println调用周围的锁上同步。当然,如果你这样做,那就AtomicInteger浪费了。

synchronized (counter) {
    System.out.println(this.threadName + ": " + counter.incrementAndGet());
}

如果您编辑您的问题以解释为什么您需要输出是连续的,那么可能有一个更好的解决方案没有这种竞争条件。

于 2012-04-09T18:36:06.867 回答
1

您需要为此同步整个构造:

synchronized(this) {    
   int counterValue = this.counter.incrementAndGet();
   System.out.println(this.threadName + ": " + counterValue);
}

但是,在这种情况下,您不必使用 AtomicInteger。平原int会工作(counter++)。

于 2012-04-09T18:38:43.303 回答
1

要按顺序打印,incAndGet 和 println 必须都在临界区,一段代码只有一个线程可以进入,其他的被阻塞。可以用二进制信号量实现,例如 java synchronized

你可以把事情放在头上,让一个线程增加一个计数器并打印它。其他线程可能在“临界区”中只占用一个接一个的计数器。这会更有效,因为关键区域应该保持较小并且最好不要 I/O。

于 2012-04-09T18:42:52.430 回答