Atomic Counters in Scala

Our primary mechanism for managing state in Scala is often through immutable data structures and functional programming patterns. However, there are scenarios where we need to manage shared mutable state across multiple threads. In this example, we’ll look at using atomic variables for thread-safe counters.

import java.util.concurrent.atomic.AtomicLong
import scala.concurrent.{Future, Await}
import scala.concurrent.ExecutionContext.Implicits.global
import scala.concurrent.duration._

object AtomicCounters {
  def main(args: Array[String]): Unit = {
    // We'll use an AtomicLong to represent our (always-positive) counter.
    val ops = new AtomicLong(0)

    // We'll start 50 futures that each increment the counter exactly 1000 times.
    val futures = for (_ <- 1 to 50) yield Future {
      for (_ <- 1 to 1000) {
        // To atomically increment the counter we use incrementAndGet.
        ops.incrementAndGet()
      }
    }

    // Wait until all the futures are done.
    Await.result(Future.sequence(futures), 5.seconds)

    // It's safe to read the final value of ops even after all futures have completed.
    println(s"ops: ${ops.get()}")
  }
}

We expect to get exactly 50,000 operations. Had we 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.

To run the program:

$ scala AtomicCounters.scala
ops: 50000

In this Scala version:

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

  2. Instead of goroutines, we use Scala’s Futures to represent concurrent computations.

  3. We use a for comprehension to create 50 futures, each incrementing the counter 1000 times.

  4. Future.sequence is used to convert our sequence of futures into a future of sequence, which we then wait for using Await.result.

  5. The incrementAndGet() method atomically increments the counter and returns the new value, similar to the Add method in the Go version.

  6. Finally, we use get() to retrieve the final value of the counter.

This example demonstrates how to use atomic variables for thread-safe operations in Scala, providing similar functionality to the atomic counters in the original Go example.