Achieve Concurrency With Akka actors
This blog describes how we can avoid difficulties to acheive concurrency in Scala using Akka actors those we are having using Java concurrency model .
Join the DZone community and get the full member experience.
Join For FreeJava comes with a built-in multi-threading model based on shared data and locks. To use this model, you decide what data will be shared by multiple threads and mark as “synchronized” sections of the code that access the shared data.
It also provides a locking mechanism to ensure that only one thread can access the shared data at a time. Lock operations remove possibilities for race conditions but simultaneously add possibilities for deadlocks. In Scala, you can still use Java threads, but the “Actor model” is the preferred approach for concurrency.
Actors provide a concurrency model that is easier to work with and can, therefore, help you avoid many of the difficulties of using Java’s native concurrency model.
Features of Actors
- When you instantiate an Actor in your code, Akka gives you an ActorRef, which you can use to send messages.
- Behind the scenes, Akka runs actors on real threads and many actors may share one thread.
- An Actor can create many actors called child actors.
- Actors interact only through asynchronous messages and never through direct method calls.
- Each actor has a unique address and a mailbox in which other actors can deliver messages.
- The actor will process all the messages in the mailbox in sequential order (the implementation of the mailbox is FIFO).
Let’s see an example,
case class Add(num1: Int, num2: Int)
case class Substract(num1: Int, num2: Int)
case class Divide(num1: Int, num2: Int)
class Calculator extends Actor {
def receive = {
case Add(num1, num2) => context.actorOf(Props[Addition]) ! Add(num1, num2)
case Substract(num1, num2) => context.actorOf(Props[Substraction]) ! Substract(num1, num2)
case Divide(num1, num2) => context.actorOf(Props[Division]) ! Divide(num1, num2)
}
}
class Addition extends Actor {
def receive = {
case Add(num1, num2) => println(num1 + num2)
}
}
class Substraction extends Actor {
def receive = {
case Substract(num1, num2) => println(num1 - num2)
}
}
class Division extends Actor {
def receive = {
case Divide(num1, num2) => println(num1 % num2)
}
}
We have a simple example of a calculator that performs three operations: addition, subtraction and division, and also have created three messages and has taken child actors accordingly. Now let's create the main method:
xxxxxxxxxx
val system = ActorSystem("Demo")
val actor1 = system.actorOf(Props[Calculator])
println("Started Calculating.....")
println("Addition")
actor1 ! Add(2, 3)
println("Substraction")
actor1 ! Substract(3, 2)
println("Divide")
actor1 ! Divide(2,3)
And the output of this is as follows:
xxxxxxxxxx
Started Calculating.....
Addition
Substraction
Divide
5
1
2
If you observe the output, the main actor sends all three messages to the actor Calculator (parent actor) at the same time and all three operations performed asynchronously.
Handling shared variables and Non-Blocking
If two actors send a message to the same actor to access the same resource at the same time, the receiving actor will keep that message inside the mailbox and will execute those messages sequentially.
The sender thread does not get blocked to wait for a return value when it sends a message to another actor.
You need not worry about synchronization in a multi-threaded environment because of the fact that all the messages are processed sequentially and actors don’t share each other’s data.
In the below diagram, while Actor B is working on the A’s message, C’s message will sit in the mailbox. After completing the execution of A’s message, it will start execution of C’s message.
Handling Failures
Akka provides supervision strategies to handle errors when an actor fails due to any reason while executing a parent actor who supervises and handles the failure.
There are two supervision strategies,
-
OneForOne
- handled case applies only for a failed child. -
actorAllForOne
- handled case applies to all siblings.
If we pass the following message to above Calculator actor then it will throw an exception:
xxxxxxxxxx
actor1 ! Divide(2, 0)
This can be handled by adding following code snippet in parent actor:
xxxxxxxxxx
override val supervisorStrategy = OneForOneStrategy(){
case _: ArithmeticException => Resume
case _ => Restart
}
I hope this blog was helpful.
Opinions expressed by DZone contributors are their own.
Comments