🎓 A1Learner
🔍
0 of 45 lessons completed

Actors at a First Look

An actor is a worker that owns its own state and handles one message at a time. Other code talks to the actor only by sending messages.

An actor collects messages in order

The actor reads each message in turn. The collected numbers are printed after the actor stops.

import java.util.concurrent.LinkedBlockingQueue
import scala.collection.mutable.ListBuffer
@main def main(): Unit =
  val mailbox = LinkedBlockingQueue[String]()
  val collected = ListBuffer.empty[Int]
  val worker = new Thread(() => {
    var running = true
    while (running) {
      val msg = mailbox.take()
      if (msg == "Stop") running = false
      else collected += msg.drop(4).toInt
    }
  })
  worker.start()
  mailbox.put("Add 10")
  mailbox.put("Add 20")
  mailbox.put("Stop")
  worker.join()
  println(collected.toList)

An actor counts its messages

The actor counts three pings and then stops. Only the final count is printed.

import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.atomic.AtomicInteger
@main def main(): Unit =
  val mailbox = LinkedBlockingQueue[String]()
  val count = AtomicInteger(0)
  val worker = new Thread(() => {
    var running = true
    while (running) {
      val msg = mailbox.take()
      if (msg == "Stop") running = false
      else if (msg == "Ping") count.incrementAndGet()
    }
  })
  worker.start()
  mailbox.put("Ping")
  mailbox.put("Ping")
  mailbox.put("Ping")
  mailbox.put("Stop")
  worker.join()
  println(count.get())

An actor keeps a running total

The actor adds each number to its total. Only the final total is printed.

import java.util.concurrent.LinkedBlockingQueue
import java.util.concurrent.atomic.AtomicInteger
@main def main(): Unit =
  val mailbox = LinkedBlockingQueue[String]()
  val total = AtomicInteger(0)
  val worker = new Thread(() => {
    var running = true
    while (running) {
      val msg = mailbox.take()
      if (msg == "Stop") running = false
      else total.addAndGet(msg.drop(4).toInt)
    }
  })
  worker.start()
  mailbox.put("Add 5")
  mailbox.put("Add 7")
  mailbox.put("Stop")
  worker.join()
  println(total.get())

Example

import java.util.concurrent.LinkedBlockingQueue
import scala.collection.mutable.ListBuffer
@main def main(): Unit =
  val mailbox = LinkedBlockingQueue[String]()
  val collected = ListBuffer.empty[Int]
  val worker = new Thread(() => {
    var running = true
    while (running) {
      val msg = mailbox.take()
      if (msg == "Stop") running = false
      else collected += msg.drop(4).toInt
    }
  })
  worker.start()
  mailbox.put("Add 10")
  mailbox.put("Add 20")
  mailbox.put("Add 30")
  mailbox.put("Stop")
  worker.join()
  println(collected.toList)
  println(collected.sum)

Key points

  • Mailbox: A mailbox is a queue where messages wait until the actor reads them.
  • Message: A message is a small value that tells the actor what to do.
  • Use a LinkedBlockingQueue as a mailbox because it safely holds messages from many senders.
  • Order: Messages leave the mailbox in the order they were sent.
  • State: The actor alone changes its own state, so no other code can change it at the same time.

In this example, the mailbox receives three add messages and then a stop message. The worker collects the numbers in order, and the program prints the collected list and then their sum.

Note

This lesson builds a small actor by hand, while real actor libraries add supervision and remote actors.

Try it Yourself

Edit the code, then press Run.

Running is simulated · Scastie

Exercise

How does other code talk to an actor?