Atomic Counters in Java

Our primary mechanism for managing state in Java is through synchronization and locks. However, there are other options for managing state. Here we’ll look at using the java.util.concurrent.atomic package for atomic counters accessed by multiple threads.

import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.CountDownLatch;

public class AtomicCounters {
    public static void main(String[] args) throws InterruptedException {
        // We'll use an AtomicLong to represent our (always-positive) counter.
        AtomicLong ops = new AtomicLong();

        // A CountDownLatch will help us wait for all threads to finish their work.
        CountDownLatch latch = new CountDownLatch(50);

        // We'll start 50 threads that each increment the counter exactly 1000 times.
        for (int i = 0; i < 50; i++) {
            new Thread(() -> {
                for (int c = 0; c < 1000; c++) {
                    // To atomically increment the counter we use incrementAndGet().
                    ops.incrementAndGet();
                }
                latch.countDown();
            }).start();
        }

        // Wait until all the threads are done.
        latch.await();

        // Here no threads are writing to 'ops', but using get() 
        // it's safe to atomically read a value even while
        // other threads are (atomically) updating it.
        System.out.println("ops: " + ops.get());
    }
}

We expect to get exactly 50,000 operations. Had we used a non-atomic long and incremented it with ops++, we’d likely get a different number, changing between runs, because the threads would interfere with each other. Moreover, we’d get data race failures when running with a race detector tool.

$ javac AtomicCounters.java
$ java AtomicCounters
ops: 50000

Next we’ll look at locks, another tool for managing state.