Understanding actor concurrency, Part 2: Actors on the JVM

Actor concurrency libraries for Scala, Groovy, and Java

In the first half of his introduction to actor concurrency, Alex Miller discussed the limitations of shared-state concurrency and explained how the actor model is expressed in Erlang. While Erlang is a nonstarter for many shops, actor implementations do exist for languages that run on the JVM. Find out how actors work and see them implemented using Scala's standard library, Groovy's GParallelizer, and the Java libraries Kilim, ActorFoundry, Actors Guild, and Jetlang.

In my previous article I introduced you to the advantages of the actor concurrency model over the shared-stated model used by popular imperative languages like Java. To help you understand actor concurrency, I explained how it's implemented in Erlang, a functional language that has been around for a couple of decades. Erlang's implementation is elegant, but it's a nonstarter if you're committed to developing applications in a language that runs on the JVM. So, in this article I'll look at Erlang-inspired actor implementations in three JVM languages: Scala, Groovy, and Java.

Table 1 is an overview of the languages and actor libraries I'll discuss in this article.

Listing 1. Actor libraries on the JVM

LanguageLibraryWeaving strategy
Scala 2.7.3Scala APIn/a
Groovy 1.6GParallelizer 0.6n/a
JavaKilim 0.6compile-time
JavaActorFoundry 1.0compile-time
JavaActors Guild 0.6runtime
JavaJetlang 0.1.7n/a

I'll use the schoolyard game Rock-Paper-Scissors as a reference application and implement it in each library to demonstrate the features and differences. (You can download the source code anytime.) Each implementation uses two Player actors and a Coordinator actor. The Coordinator requests each player to play, and each Player replies with Rock, Paper, or Scissors. The Coordinator accepts the responses, announces the winner, and starts over. The message flow is illustrated in Figure 1.

Rock-Paper-Scissors message flow
Figure 1. Rock-Paper-Scissors message flow

Actors in Scala

Scala features the most faithful implementation of the Erlang actor model on the JVM and has made it a central part of Scala's concurrency story. Scala is a hybrid language that takes cues from both the object-oriented and functional programming traditions. It is designed to be extensible and to grow into new language features as needed. The language itself is built on a small core of principles, but it has a much larger perceived surface area thanks to these "scalable" language capabilities.

In fact, the Scala actors implementation is provided as part of the Scala standard library (equivalent to the JDK in Java), not as part of the language. It replicates much of the Erlang actor model. The fact that this is possible to do well as a library is a testament to Scala's flexibility.

Scala actors support all of the key concepts from Erlang. Actors are defined as a trait in the Scala standard library. In Scala, a trait is a set of method and field definitions that can be mixed into other classes. This is similar to providing an abstract superclass in Java but more flexible, particularly in comparison to multiple inheritance.

Listing 1 defines a Scala actor. The act() method is part of the contract of the Actor trait and is analogous to the function executed by an actor spawned in Erlang.

Listing 1. Scala actor definition

class Player() extends Actor {
  def act() {
    // message handling loop
  }
}

Before looking in more detail at the actors, we first need to define some messages, as shown in Listing 2. In Scala, a class is a type definition, and you can create instances with new just as in Java. You can also define a type as an object, which means it is always a true singleton instance. To do pattern matching, Scala has a concept of case classes and case objects. Case classes are defined with a special case modifier that causes the compiler to add some extra code to the class. Actors perform pattern matching on messages received from their inboxes, so it is common to use case classes to define messages.

Listing 2. Scala message definitions

abstract case class Move
case object Rock extends Move
case object Paper extends Move
case object Scissors extends Move

case object Start
case class Play(sender:Actor)
case class Throw(player:Actor, move:Move)

Listing 2 defines an abstract Move class and three singleton implementations of that class representing rock, paper, and scissors. In Java you might use an enum in this case (as you'll see in later examples). Listing 2 also defines a singleton Start message and classes for Play and Throw message classes.

The Coordinator actor, defined in Listing 3, first creates the two Player actors and starts them. In Erlang, actors frequently loop by making a tail-recursive call back to the original function. In Scala, the loop { } construct provides this looping behavior.

Listing 3. The Coordinator actor

class Coordinator() extends Actor {
  def act() {
    val player1 = new Player().start
    val player2 = new Player().start

    loop {
      react {
        case Start => {
          player1 ! Play(self)
          player2 ! Play(self)
        }
        case Throw(playerA, throwA) => {
          react {
            case Throw(playerB, throwB) => {
              announce(playerA, throwA, playerB, throwB)
              self ! Start
            }
          }
        }
      }
    }
  }

Within the loop, you will typically see either react { } or receive { }. The react behavior, similar to Erlang, provides a lightweight actor that is not bound to a thread. The receive behavior creates an actor that is actually bound to a java.lang.Thread (and ultimately to a kernel thread). Which model you choose will depend on your application. It's nice to have the flexibility to decide which actors must be bound to real threads and which can be lightweight.

Every actor has an internal mailbox where messages are received from other actors and consumed within the actor. Messages are matched using pattern matching, one of Scala's most powerful features. In the Coordinator actor, we see our case classes being used during message matching. Messages are sent asynchronously with the ! operator, just as in Erlang: actor ! message. A few other operators are also provided:

  • !? sends a message and waits for a reply (synchronous).
  • !! sends a message and returns a Future for retrieving the reply.

The Scala support for actors is quite strong, building firmly on the Erlang model. Writing actors code is a natural part of writing Scala code, and the Scala library has full support not just for actor definition, pattern matching, and flexible message send, but also for many of the Erlang-style error-handling primitives. An implementation of the Erlang OTP Supervisor functionality and a distributed version using Terracotta also exist.

GParallelizer: Actor support for Groovy

GParallelizer is a relatively new library that provides actor support in Groovy. It benefits from Groovy's support for dynamic typing and closures to make writing actors straightforward. And it takes full advantage of the upcoming Java 7 fork/join and ParallelArray functionality to ease the writing of concurrent code.

GParallelizer also supports both thread-bound and event-driven actors. Listing 4 shows how we can create an anonymous event-driven actor with GParallelizer for our Coordinator actor.

Listing 4. Event-driven actors in GParallelizer

def coordinator = PooledActors.actor {
  loop {
    react {
      player1.send("play")
      player2.send("play")

      react {msg1 ->
        react {msg2 ->
          announce(msg1[0], msg1[1], msg2[0], msg2[1])
          send("start")
        }
      }
    }
  }
}.start()

You'll notice that the syntax is (intentionally) very similar to the Scala example. The major difference here is that instead of !, we must execute the send() method, and we must roll our own pattern matching with standard Groovy control constructs. (Scala's high-quality pattern matching is one of its strongest features, so this should not be a surprising difference.)

GParallelizer's extremely lightweight style of actor creation doesn't even require a class here, because you can define the actor anonymously and directly assign it to the coordinator variable.

GParallelizer is also unique in its support for simplifying concurrency code based on Java Executors and the JSR 166y ParallelArray library. This doesn't really have anything to do with actors, but its nice to see a variety of concurrency approaches supported.

Three actor frameworks for Java

Now we move on to the Java libraries. All of them have a fundamental obstacle to overcome. Scala and Groovy can create lightweight actors by relying on scheduling over a thread pool of actors defined by partial functions or closures. In Java, these techniques don't exist as part of the language, so most of the Java actor libraries rely on bytecode modification either at compile time or runtime.

Kilim

Kilim is an actor framework that relies on the two concepts of Task and Mailbox. To create an actor, your class should extend Task, but an actor does not have a mailbox created as part of the Task. Instead, you must create and provide Mailboxes between actors as needed by the application. This is a more flexible model, but for simple patterns it feels like more effort is required.

Mailboxes use generics, and you must define messages passed through mailboxes as classes. Listing 5 illustrates the Player actor in Kilim. It must be initialized with its inbox. The Coordinator's mailbox is passed inside the PlayMessage for a reply.

Listing 5. Player actor in Kilim

public class Player extends Task {
  private static final Random RANDOM = new Random();
  private final String name;
  private final Mailbox<PlayMessage> inbox;

  public Player(String name, Mailbox<PlayMessage> inbox) {
    this.name = name;
    this.inbox = inbox;
  }

  public void execute() throws Pausable {
    while(true) {
      PlayMessage message = inbox.get();
      message.getResponseMailbox().putnb(new ThrowMessage(name, randomMove()));
    }
  }

  private Move randomMove() {
    return Move.values()[RANDOM.nextInt(Move.values().length)];
  }
}

In older versions of Kilim, a @pausable annotation was used to indicate on the execute() method that the compile-time weaver should do it's magic. In the latest version, that marker has been replaced with the throws Pausable marker on execute().

I found it was easy to write code with Kilim, but it was more complicated to integrate into my tool chain because of the need to run a compile-time weaver. That has also been my experience when I've dealt with other tools that required compile-time modification.

ActorFoundry

ActorFoundry is a different take on actors in Java. It piggybacks on Kilim by reusing the Kilim compile-time weaver. However, it provides a different API and a different runtime execution environment. The same Player example from Listing 5 is reworked in Listing 6 for the ActorFoundry.

Listing 6. ActorFoundry actors

public class Player extends Actor {
  // etc

  @message public void play(ActorName coordinator) {
    send(coordinator, "playerThrows", name, randomMove());
  }
}

In ActorFoundry, each allowed incoming message is defined by a method annotated by @message. There is no receive loop or pattern matching; instead, ActorFoundry just has messages. Messages are sent to an actor (represented by the ActorName class) using the send() method.

The send() method takes the actor, the message name, and the set of arguments to pass as the message. Being able to use methods directly and pass multiple arguments actually removes the need to define message classes in most cases, which can simplify the code somewhat compared to other options. However, needing to specify method names as strings is a strong code smell indicating a dependence on reflection and hampering the ability to do automated refactoring.

ActorFoundry does both code generation and weaving with Kilim's weaver, which makes for an even more complicated development process than Kilim. I got things working by copying the Ant configuration from the ActorFoundry project and modifying paths as appropriate.

ActorFoundry provides a full runtime environment you must populate with your actor definitions. When you start ActorFoundry, you specify the initial actor message that kicks off the system; after that, everything is done through actors. Thus, you must allow ActorFoundry to structure the execution of your entire application. The combination of the complex compile-time tooling and the surrender of your control of runtime startup and integration require a deep commitment to the ActorFoundry model.

Another difference with ActorFoundry is that it uses a license that is fairly open but only for noncommercial and educational uses.

Actors Guild

Actors Guild is a Java actor framework that relies on runtime bytecode modification with the ASM library and thus is easier to integrate into your project than Kilim or ActorFoundry.

Similar to ActorFoundry, the Actors Guild framework relies on annotated methods for each possible incoming actor message. Each message method must return an AsyncResult parameterized with a reply message (which can be Void for no reply). The Actors Guild Actor base class provides methods such as result(), which can wrap your return value automatically.

1 2 Page
Recommended
Join the discussion
Be the first to comment on this article. Our Commenting Policies
See more