Atomic Counters in Logo

In Java, we can use the java.util.concurrent.atomic package for atomic operations. Here’s an example of using 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 integer 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 conditions.

To run the program:

$ javac AtomicCounters.java
$ java AtomicCounters
ops: 50000

This example demonstrates the use of atomic operations in Java for managing state across multiple threads. The AtomicLong class provides thread-safe operations without the need for explicit synchronization.

Next, we’ll look at locks, another tool for managing state in concurrent Java programs.