Atomic Counters in CLIPS

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(0);

        // 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 it's safe to atomically read 
        // a value even while other threads might be updating it.
        System.out.println("ops: " + ops.get());
    }
}

This Java program demonstrates the use of atomic counters, which are thread-safe variables that can be safely accessed and modified concurrently by multiple threads.

In Java, we use the java.util.concurrent.atomic package to work with atomic variables. In this example, we use AtomicLong as our counter.

Instead of Go’s WaitGroup, we use a CountDownLatch to wait for all threads to complete their work.

We create 50 threads (which are Java’s equivalent to goroutines in this context), each incrementing the counter 1000 times. The incrementAndGet() method is used to atomically increment the counter.

After all threads have finished, we print the final value of the counter.

To run this program:

$ javac AtomicCounters.java
$ java AtomicCounters
ops: 50000

We expect to get exactly 50,000 operations. If we had 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 encounter race conditions.

Java’s AtomicLong (and other atomic classes) provide a way to perform atomic operations without explicit synchronization, similar to Go’s sync/atomic package. This is particularly useful for implementing lock-free algorithms and data structures.

Next, we’ll look at other synchronization primitives in Java for managing shared state.