Atomic Counters in Groovy

In Groovy, we can use the java.util.concurrent.atomic package to work with atomic counters. Here’s an example that demonstrates the use of atomic counters accessed by multiple threads:

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

// We'll use an AtomicLong to represent our (always-positive) counter.
def ops = new AtomicLong(0)

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

// We'll start 50 threads that each increment the counter exactly 1000 times.
50.times {
    Thread.start {
        1000.times {
            // To atomically increment the counter we use incrementAndGet().
            ops.incrementAndGet()
        }
        latch.countDown()
    }
}

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

// It's safe to atomically read the value even while
// other threads might be (atomically) updating it.
println "ops: ${ops.get()}"

Let’s break down the code and explain its components:

  1. We import AtomicLong from the java.util.concurrent.atomic package, which provides atomic operations on long values.

  2. We also import CountDownLatch from java.util.concurrent, which we’ll use to wait for all threads to complete.

  3. We create an AtomicLong called ops to represent our counter.

  4. We create a CountDownLatch initialized with a count of 50, corresponding to the number of threads we’ll create.

  5. We use the Groovy times method to start 50 threads. Each thread:

    • Increments the counter 1000 times using incrementAndGet().
    • Calls countDown() on the latch when it’s done.
  6. After starting all threads, we call await() on the latch to wait for all threads to complete.

  7. Finally, we print the value of the counter using get().

When you run this script, you should see:

ops: 50000

We expect to get exactly 50,000 operations. If we had used a non-atomic integer and incremented it with regular addition, we’d likely get a different number, changing between runs, because the threads would interfere with each other.

This example demonstrates how to use atomic operations in Groovy to safely manipulate a shared counter from multiple threads. It’s worth noting that while Groovy doesn’t have built-in constructs like goroutines, it can leverage Java’s robust concurrency utilities.

Next, we could explore other concurrency tools available in Groovy and Java, such as locks and synchronized blocks.