Akka behavior in different levels of detail
As we went through the Akka internal behavior in previous articles, let's review it from a high/conceptual level to a low/internal level where you see an Akka application as a huge
ForkJoinTask application (although it doesn't use fork-join mechanism).
Previous articles related to this post are here:
- Local Actor workflow part 1 - Sender side
- Local Actor workflow part 2 - Receiver side
- Dispatcher behavior
- Mailbox and ForkJoinTask
The highest level: Actors pass messages
If you ever heard of Akka, or an actor model in general, you might know that actors, which are minimal components consisting of your entire application, communicate to each other by passing messages.
This is usually what people would mention when they try to explain the actor model to those who never heard of it.
The second level: Actor's ! and receive methods
The next level touches something specific to Akka. If you have experience programming an application using Akka, you would know that Akka provides:
ActorRefto send a message to an
Actorwhich you need to implement in your concrete
Actorclass, and the
receivemethod processes incoming messages
For those who don't need to interact with Akka day to day, knowing what the
receive methods are helps them understand Akka-based applications written by someone else.
Or with this level of knowledge, you can still implement your important (so-called domain or business) logic for your application inside the
receive method. Then Akka takes care of actual execution of the
receive method in a multi-threaded environment, but you are not yet exposed to how threads are employed by Akka to power your application.
Let’s go to the next level for more serious Akka users. We are going to look at
The third level: MessageQueue
MessageQueue in Akka is something sits in-between your sender
Actor and the receiver
Akka makes you avoid your sender
Actor call the receiver
Actor method directly. There is no direct interaction between
Actor instances. Instead, like you saw in the previous level,
! method is used to communicate with other actors, and that method internally puts your messages into
MessageQueue, before the receiver actor pick them up. That allows you execute the sender and receiver
Actors work concurrently.
The documentation mentions that
Mailbox, which has the associated
MessageQueue implementation, can be configured based on your usage. All available
MessageQueue implementations used by Akka are chosen so that they can be accessed from different threads concurrently.
When you write a concurrent application, it is generally hard to program your own class safely against access from multiple threads, especially as your class grows to be big and complicated. Instead, a lot of researchers have come up with thread-safe algorithms and implementations of data classes focusing on simple and fundamental ones. Queues are typical examples of such data classes where thread-safe implementations are available.
So, Akka's approach is to put concurrency concerns within
MessageQueue which Akka takes care of, and provide avaialble
MessageQueue implementations already. As long as you follow the pattern in the Akka actor model, and use immutable messages, you don't need to worry about concurrency inside each
The second-lowest level: Dispatcher and ForkJoinTask
Now you know that Akka
Actors communicate with each other via
MessageQueue, but how does it actually use threads to execute the code inside
Actor? Still, something needs to execute your code inside
Actor and that's a dedicated thread provided by the undelying
That is illustrated in the above short video, and also discussed in these two other articles.
ExecutorService schedules a
ForkJointTask to be run on in a pool of threads, and that
ForkJoinTask is actually an Akka (internal)
Mailbox extends ForkJointTask.
run method eventually invokes the
receive method of your
The lowest level: Akka application as huge ForkJoinTask application
Taking a step further, looking at this from the
ExecutorService point of view:
you can see your Akka application as a huge
ForkJoinTask application, where you excecute your domain/business logic from
One caveat is that although it is
ForkJoinTask, Akka does not use fork-join mechanism to execute the
Actor internal code. (i.e.) Akka doesn't use
invokeAll methods from
ForkJoinTask but uses the simple
run method, in an event style which is described in the middle of
ForkJoinPool is the default
ExecutorService for the default
Dispatcher. The reason why
ForkJoinPool was chosen as default was its performance considering Akka's use cases. More detail about the reason can be found in previous Akka's official blog, LET IT CRASH - Scalability of Fork Join Pool.
From here, you can even go deeper, outside of/below Akka, like how Java's
ForkJoinPool work or even how OS schedules tasks on multiple threads. Those are out of scope of this article, but if you are interested, please go ahead! (hopefully I might cover them at some point later).
- Javadoc of
java.util.concurrent.ForkJoinTaskat - https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/ForkJoinTask.html
- Official documentation of Akka Mailbox at https://doc.akka.io/docs/akka/current/mailboxes.html
- Official documentation of Akka Dispatcher at https://doc.akka.io/docs/akka/2.5/dispatchers.html
- A LET IT CRASH blog post explaining efficiency of
ForkJoinPool- Scalability of Fork Join Pool
- A discussion with Doug Lea, linked from the above LET IT CRASH blog article, who lead the design and implementation of Java's