为了通过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也是如此。
我有一种感觉,我错过了一些非常明显的东西。
答案 0 :(得分:7)
当看到incrementAndGet()方法导致JVM在打印当前线程的更新值之前上下文切换到另一个线程以进行更新时,会出现问题
这种情况通常会在这些情况下发生。尽管AtomicInteger
计数器正在递增,但在增量发生之后没有任何内容可以阻止Thread 2
被移出,而之前 {{1>} 1}}被称为。
println
如果要在100%的时间内按顺序打印“计数器”,则必须同时锁定增量和 int counterValue = this.counter.incrementAndGet();
// there is nothing stopping a context switch here
System.out.println(this.threadName + ": " + counterValue);
调用。当然,如果你这样做,那么println
就会被浪费。
AtomicInteger
如果你编辑你的问题来解释为什么你需要输出是连续的,那么可能有一个没有这种竞争条件的更好的解决方案。
答案 1 :(得分:1)
您需要同步整个构造:
synchronized(this) {
int counterValue = this.counter.incrementAndGet();
System.out.println(this.threadName + ": " + counterValue);
}
但是,在这种情况下,您不必使用AtomicInteger。普通int
可以工作(反++)。
答案 2 :(得分:1)
要按顺序打印,incAndGet和println必须都在关键区域中,一段代码只能有一个线程进入,其他线程被阻止。例如,可以使用二进制信号量实现,例如java synchronized
。
你可以把头上的东西转过来,让一个线程递增一个计数器并打印出来。其他线程可能在“关键区域”中只接受一个接一个的计数器。这样会更有效率,因为关键区域应保持较小,最好不要I / O.