Concurrency

Futures are eager (start immediately) and memoized; they need an ExecutionContext.

33.1 JVM threads (low level)

val t = Thread(() => println(s"running on ${Thread.currentThread().getName}"))
t.start(); t.join()

@volatile var running = true
val lock = Object()
lock.synchronized { /* critical section */ }

import java.util.concurrent.atomic.AtomicInteger
val counter = AtomicInteger(0)
counter.incrementAndGet()

import java.util.concurrent.{Executors, ConcurrentHashMap}
val pool = Executors.newFixedThreadPool(4)
val vt = Thread.ofVirtual().start(() => println("virtual thread"))   // JDK 21 virtual threads

33.2 Futures & Promises

Futures are eager (start immediately) and memoized; they need an ExecutionContext. Use blocking { ... } around blocking calls.

import scala.concurrent.{Future, Promise, Await}
import scala.concurrent.duration.*
import scala.concurrent.ExecutionContext.Implicits.global

val f1 = Future { Thread.sleep(500); 21 }
val f2 = Future { 2 }

val combined: Future[Int] = for
  a <- f1
  b <- f2
yield a * b

combined.onComplete {
  case scala.util.Success(v) => println(s"Result: $v")
  case scala.util.Failure(e) => println(s"Failed: $e")
}

Await.result(combined, 2.seconds)        // only at program edges / tests!

Future.sequence(List(f1, f2))            // Future[List[Int]]
Future.traverse(List(1, 2, 3))(n => Future(n * 2))
Future.firstCompletedOf(List(f1, f2))
f1.recover { case _: Exception => 0 }
f1.recoverWith { case _ => Future.successful(0) }
f1.fallbackTo(f2)
f1.zip(f2)
f1.transform(_.map(_ + 1))

val p = Promise[String]()
p.success("done")       // completes p.future

33.3 Parallel collections

//> using dep org.scala-lang.modules::scala-parallel-collections:1.0.4
import scala.collection.parallel.CollectionConverters.*
(1 to 1_000_000).par.map(_ * 2).sum

33.4 Concurrency hazards

Race conditions, deadlocks, visibility issues. Prefer immutable data, message passing (actors), or effect systems with structured concurrency (fibers, Ref, Deferred, Queue).