Achieving Concurrency with Akka Actors

Reading Time: 3 minutes

Java 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

  1. When you instantiate an Actor in your code, Akka gives you an ActorRef, which you can use to send messages.
  2. Behind the scenes, Akka runs actors on real threads and many actors may share one thread.
  3. A Actor can create many actors called child actors.
  4. Actors interact only through asynchronous messages and never through direct method calls.
  5. Each actor has a unique address and a mailbox in which other actors can deliver messages.
  6. 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)
    }
  }

The output of this is as follows, 

Started Calculating.....
Addition
Substraction
Divide
5
1
2

If you observe the output, 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 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 working on the A’s message, C’s message will sit in the mailbox. After completing 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 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,

actor1 ! Divide(2, 0)

This can be handled by adding following code snippet in parent actor –

override val supervisorStrategy = OneForOneStrategy(){
      case _: ArithmeticException => Resume
      case _ => Restart
    }

I hope this blog was helpful.

References

Knoldus-blog-footer-image