Using AtomicInteger as a static shared counter

2019-05-04 02:14发布

问题:

In an effort to learn about synchronization via Java, I'm just messing around with some simple things like creating a counter shared between threads.

The problem I've run into is that I can't figure out how to print the counter sequentially 100% of the time.

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

The above increments the AtomicInteger counter, gets the new value, and prints it to the console identified by the thread name that is responsible for that update. The problem occurs when it appears that the incrementAndGet() method is causing the JVM to context switch to another thread for its updates before printing the current thread's updated value. This means that the value gets incremented but not printed until the thread returns to the executing state. This is obvious when looking at this example output:

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

You can see that when execution returns to Thread 1, it prints its value and continues updating. The same is evident with Thread 2.

I have a feeling that I'm missing something very obvious.

回答1:

The problem occurs when it appears that the incrementAndGet() method is causing the JVM to context switch to another thread for its updates before printing the current thread's updated value

This is a race condition that often can happen in these situations. Although the AtomicInteger counters are being incremented properly, there is nothing to stop Thread 2 from being swapped out after the increment happens and before the println is called.

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

If you want to print the "counter sequentially 100% of the time" you are going to have to synchronize on a lock around both the increment and the println call. Of course if you do that then the AtomicInteger is wasted.

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

If you edit your question to explain why you need the output to be sequential maybe there is a better solution that doesn't have this race condition.



回答2:

You need to synchronize the whole construction for that:

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

In this case, though, you don't have to use AtomicInteger. Plain int would work (counter++).



回答3:

To print sequentially, the incAndGet and the println must both be in a critical region, a piece of code only one thread may enter, the others being blocked. Realisable with a binary semaphore, for instance, like java synchronized.

You could turn things on the head, and have one thread incrementing a counter and printing it. Other threads may in a "critical region" take only one after another the counter. That would be more efficient, as critical regions should remain small and preferable do no I/O.