Akka Actor is a concurrency framework built in Scala and Java, aiming to provide a simple and straightforward abstraction to model your components, while decoupling multi-threading away from the business logic.
With Akka Actor, you can model your components naturally and straightforwardly, just like designing a role in the real world who can receive a set of specific messages, react on each message correspondingly, and optionally send one or more messages to other coworkers. This allows you to focus on how the roles behave while designing your system, and Akka will take care of managing the concurrent execution.
Akka Actor also offers efficient concurrency for your system. Threads and work scheduling are managed by Akka itself, where Akka use threads only on the actors that have actual works to do for particular actors, hence not holding threads waiting and wasting resources. Communication between actors are usually via sending messages to the mailbox of the target actor, and the actor will process the messages from its mailbox sequentially in a FIFO order. Therefore we can model our system in an asynchronous style quite naturally. (But Akka does provide you approaches for synchronous communication if you need.)
Now let’s look at the source code to understand how this works.
Basic functionality is defined in a
Actor. The core method is
receive, that’s where we define how the actor reacts to each type of the message it supports. It’s a partial function so you can implement each message type per
case and add new
cases when new message types come. The trait also provides lifecycle hook methods to allow you customize lifecycle control on the actor.
Typically we should access an
Actor only via its
ActorRef, which refers to the
Actor implementation and provides some common functionality like the path to the actor, method to send/forward message to this
Actor etc. The
ActorRef is further encapsulated in an
ActorCell which contains the context around the actor, including:
ActorRefreferring to itself
Propsobject used when creating the
MessageDispatcherwhich handles how to schedule actors to work on the thread pool
ActorRefto its parent actor
behaviorStackto keep a collection of behaviours defined on the actor in a LIFO order
sysmsgStashto temporarily stash system messages
ActorCell also has access to the
MailBox associated to its actor. That is defined in the
After having a look at how the basic skeleton of Akka actor is, let’s take a tour to see how an Actor is created and started processing, also how communication between actors works.
Actors run in
ActorSystem. The first thing is to start the ActorSystem before we can spawn actors inside it. The implementation is in
ActorSystem is bootstrapped, initially it contains only one “guardian” actor, and all the other actors in the same actor system will be the guardian’s direct or indirect children. This allows us to organize our actors in a hierarchical structure where a parent actor supervises and manages its own child actors, and avoid the guardian being overwhelmed by managing large quantity of actors.
ActorSystem.actorOf(props: Props) method will create a top-level actor. It delegate the real creation to the
ActorRefProvider, then call
ActorRef.start() to bootstrap the actor and return the actor reference.
Then what does the
ActorRef.start() do? It delegates the call to
dungeon.Dispatch, one of the traits that
It says “start off the actor by scheduling its mailbox”, not “scheduling the actor cell”?
It turns out that,
Mailbox, rather than
Actor, is the task submitted to thread pool or fork-join pool.
Mailbox.run() shows that when executed, the mailbox first check whether itself is closed, if not then process all the system messages, followed by processing messages in the mailbox queue. When it finished processing messages, we need to reschedule the mailbox for next execution. It will be re-submitted to
ExecutionService if there’s still unprocessed system or mailbox messages in the mailbox.
The process of handling system messages and mailbox messages are quite similar, except that all system messages in queue are processed at once but mailbox ones are processed one at a time. A deadline is set to prevent a mailbox from using the thread for too long and causing others waiting for too long.
That’s what happens when we create and start a new actor. Next we’ll see how a message is sent to an actor.
You can send any immutable object as a message to an
ActorRef, by calling
! method like below:
actorA ! DoA("details...")
! method then call sendMessage on the corresponding ActorCell, which is implemented in
So every time we send a message to an actor’s mailbox, the mailbox will be scheduled by
ExecutorService to process when thread resource is available.
While reading the source code, the basic understandings I got are as below:
- Akka Actor framework uses
ExecutorServices to concurrently schedule and execute messages in the mailboxes of actor instances.
- It’s the mailbox that implements
Runnableand got executed by the
ExecutorServices. When executing, each mailbox apply the corresponding
Actor.receivefunction to messages in the mailbox in a FIFO approach.
- You can think of an actor as a
Runnabletask that has a queue to store messages sent to it, and defines behaviours on how to process each type of message it supports. The tasks can communicate only via sending messages.
- The dispatching of messages happen concurrently without ordering guarantee, but messages received by a specific actor will be processed in a sequential order.