Actor
Akka Actor Tutorial: Untyped Actors
Akka is a powerful toolkit and runtime for building concurrent, distributed, and fault-tolerant applications on the JVM. In this tutorial, we'll explore Akka's untyped actors, which were the default actors in Akka before the introduction of typed actors in Akka 2.6.
1. Setting Up the Project
First, create a new Scala project and add the Akka dependencies to your build.sbt:
libraryDependencies += "com.typesafe.akka" %% "akka-actor" % "2.6.x"
Replace x with the latest minor version of Akka.
2. Basic Concepts
- Actor: A unit of computation that processes messages asynchronously.
- Actor System: A hierarchical group of actors that share common configuration.
- Message: Immutable data passed between actors.
- Mailbox: Queue where incoming messages are stored until processed by the actor.
3. Creating an Actor System
Start by creating an ActorSystem, which will manage and supervise actors:
import akka.actor.{ActorSystem, Props, Actor, ActorRef}
// Create an Actor System
val system = ActorSystem("my-actor-system")
4. Creating a Simple Actor
To define an untyped actor, extend the Actor trait and implement the receive method, which handles incoming messages:
class SimpleActor extends Actor {
def receive: Receive = {
case "hello" => println("Hello, World!")
case "goodbye" => println("Goodbye, World!")
case msg: String => println(s"Received message: $msg")
}
}
5. Instantiating and Sending Messages to an Actor
Actors are instantiated using the Props class, and messages are sent using the ! (tell) operator:
val simpleActor: ActorRef = system.actorOf(Props[SimpleActor], "simple-actor")
// Send messages to the actor
simpleActor ! "hello"
simpleActor ! "How are you?"
simpleActor ! "goodbye"
6. Actor Lifecycle
Actors have a lifecycle managed by the Akka framework:
- Pre-start: Executed when the actor is created.
- Post-stop: Executed when the actor is stopped.
- Restart: Executed when the actor is restarted after a failure.
You can override lifecycle hooks in your actor:
class LifecycleActor extends Actor {
override def preStart(): Unit = println("Actor is starting")
override def postStop(): Unit = println("Actor has stopped")
def receive: Receive = {
case "stop" => context.stop(self)
case msg => println(s"Received: $msg")
}
}
val lifecycleActor: ActorRef = system.actorOf(Props[LifecycleActor], "lifecycle-actor")
lifecycleActor ! "test"
lifecycleActor ! "stop"
7. Actor Hierarchy and Supervision
Actors are organized hierarchically, with parent actors supervising child actors. Supervision strategies define how to handle child actor failures (e.g., restart, stop, resume).
Example of creating child actors:
class ParentActor extends Actor {
override def preStart(): Unit = {
val child = context.actorOf(Props[SimpleActor], "child-actor")
child ! "hello"
}
def receive: Receive = {
case _ =>
}
}
val parentActor: ActorRef = system.actorOf(Props[ParentActor], "parent-actor")
8. Stopping Actors
Actors can be stopped using context.stop(actorRef) or PoisonPill:
simpleActor ! PoisonPill
Stopping an actor triggers its postStop lifecycle hook.
9. Handling Actor Failure
You can define a supervisor strategy to handle child actor failures:
import akka.actor.SupervisorStrategy._
import akka.actor.OneForOneStrategy
class SupervisorActor extends Actor {
override val supervisorStrategy = OneForOneStrategy() {
case _: Exception => Restart
}
def receive: Receive = {
case _ =>
}
}
10. Graceful Shutdown
To gracefully shut down an ActorSystem, use:
system.terminate()
11. Example: Simple Chat System
Let's create a simple chat system with actors representing users and a chat room.
User Actor
class UserActor(name: String) extends Actor {
def receive: Receive = {
case msg: String => println(s"$name received: $msg")
}
}
Chat Room Actor
class ChatRoomActor extends Actor {
var users: Set[ActorRef] = Set()
def receive: Receive = {
case Join(user) => users += user
case Leave(user) => users -= user
case Broadcast(msg) => users.foreach(_ ! msg)
}
}
// Messages
case class Join(user: ActorRef)
case class Leave(user: ActorRef)
case class Broadcast(message: String)
Main
val chatRoom: ActorRef = system.actorOf(Props[ChatRoomActor], "chat-room")
val user1: ActorRef = system.actorOf(Props(new UserActor("Alice")), "user1")
val user2: ActorRef = system.actorOf(Props(new UserActor("Bob")), "user2")
chatRoom ! Join(user1)
chatRoom ! Join(user2)
chatRoom ! Broadcast("Hello, everyone!")
chatRoom ! Leave(user1)
chatRoom ! Broadcast("Goodbye, Alice!")
system.terminate()