Atomic Counters in Kotlin

In Kotlin, we can use atomic operations for managing state across multiple coroutines. Let’s look at using the java.util.concurrent.atomic package for atomic counters accessed by multiple coroutines.

import kotlinx.coroutines.*
import java.util.concurrent.atomic.AtomicLong

suspend fun main() = coroutineScope {
    // We'll use an AtomicLong to represent our (always-positive) counter.
    val ops = AtomicLong(0)

    // We'll start 50 coroutines that each increment the counter exactly 1000 times.
    val jobs = List(50) {
        launch {
            repeat(1000) {
                // To atomically increment the counter we use incrementAndGet().
                ops.incrementAndGet()
            }
        }
    }

    // Wait until all the coroutines are done.
    jobs.forEach { it.join() }

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

Let’s break down the key components:

  1. We use AtomicLong from java.util.concurrent.atomic package to represent our counter.

  2. Instead of Go’s WaitGroup, we use Kotlin’s structured concurrency with coroutineScope and launch.

  3. We create 50 coroutines using List(50) and launch, each incrementing the counter 1000 times.

  4. The incrementAndGet() method is used to atomically increment the counter.

  5. We wait for all coroutines to complete using jobs.forEach { it.join() }.

  6. Finally, we safely read the final value using ops.get().

To run this program:

$ kotlinc atomic-counters.kt -include-runtime -d atomic-counters.jar
$ java -jar atomic-counters.jar
ops: 50000

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

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