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.

Listing 7 shows a play() method annotated with @Message that acts as an incoming "play" message. It returns a reply to the caller as a ThrowMessage containing the player's name and move. Each incoming message will be modeled as a method.

Listing 7. Actors Guild example

public abstract class Player extends Actor {
  @Prop
  public abstract String getName();
  public abstract void setName(String name);

  @Message
  public AsyncResult<ThrowMessage> play() {
    return result(new ThrowMessage(getName(), randomMove()));
  }

  // etc
}

In addition to the play() method in Listing 7, you might notice that the Player class is abstract and uses the @Prop annotation on the name property getter and setter. Actors Guild includes a dependency-injection framework for the creation and setup of the actor instances.

The runtime instrumentation that Actors Guild does is an advantage over Kilim and ActorFoundry because it simplifies the integration into existing development environments. In a Java library, the use of methods to handle message receive is a very natural mapping and works well. Between Actors Guild and ActorFoundry, I prefer the Actors Guild approach, which pushes you toward a single class defining the message and away from reflective strings.

However, I don't like having dependency injection mixed up in my actor code. Popular frameworks such as Guice and Spring already handle dependency injection in robust ways; I am wary of a new similar solution wedged into a library with different goals. For example, needing to declare actor classes as abstract classes and not being allowed to define a constructor are drawbacks. These constraints mean that to do any testing on the actor, you must subclass it and provide the abstract methods or else use the Actors Guild factories to generate the actor instance.

Building actor-like behavior with Jetlang

Finally, we look at the Jetlang library, which technically does not provide an actor framework but rather a set of building blocks from which actor-like functionality (as well as other architectures) can be assembled. Jetlang is based on the older Retlang library for .NET.

Jetlang has three primary concepts:

  • Fiber - a lightweight thread-like construct
  • Channel - a way to pass messages between Fibers
  • Callback - a function to be invoked when a message arrives on a Channel in the context of a Fiber

These building blocks are enough to assemble the concept of an actor. I created an Actor class, shown in Listing 8, that holds a Fiber, a Channel to use as an inbox, and a Callback function to call when messages arrive in the inbox. It also provides a send() method to put a message on the actor's inbox queue.

Listing 8. An actor in Jetlang

public class Actor<T> {
  private final Channel<T> inbox;
  private final Fiber fiber;
  private final Callback<T> callbackFunction;

  public Actor(Fiber fiber, Channel<T> inbox, Callback<T> callbackFunction) {
    this.fiber = fiber;
    this.inbox = inbox;
    this.callbackFunction = callbackFunction;
  }

  public Channel<T> inbox() {
    return this.inbox;
  }

  public Fiber fiber() {
    return this.fiber;
  }

  public Callback<T> callbackFunction() {
    return this.callbackFunction;
  }

  public void send(T message) {
    this.inbox.publish(message);
  }
}

I then created an ActorFactory, shown in Listing 9, that can be used to create new actors from a Callback function. This simplifies the process of obtaining a Fiber and creating the Actor.

Listing 9. ActorFactory

public class ActorFactory {
  private final ExecutorService threads;
  private final PoolFiberFactory fiberFactory;

  public ActorFactory() {
    threads = Executors.newCachedThreadPool();
    fiberFactory = new PoolFiberFactory(threads);
  }

  public Fiber startFiber() {
    Fiber fiber = fiberFactory.create();
    fiber.start();
    return fiber;
  }

  public <T> Channel<T> createInbox() {
    return new MemoryChannel<T>();
  }

  public <T> Actor<T> createActor(Callback<T> actorCallback) {
    Fiber fiber = startFiber();
    Channel<T> inbox = createInbox();
    inbox.subscribe(fiber, actorCallback);
    return new Actor<T>(fiber, inbox, actorCallback);
  }
}

The actor classes are then defined as a Callback, as in Listing 10.

Listing 10. Jetlang Player callback function

public class Player implements Callback<PlayMessage> {
  private final String name;

  public Player(String name) {
    this.name = name;
  }

  public void onMessage(PlayMessage message) {
    message.getResponseActor().send(
      new ThrowMessage(name, randomMove()));
  }

  // etc
}

In conclusion

For a pure actor approach, I find Scala to be the strongest implementation in terms of both similarity to Erlang and maturity. Among the Java alternatives, I like the Jetlang library the best in some ways, because it provides a consistent model that doesn't feel awkward or unnatural in Java (in either tooling or usage). It requires a bit more thinking initially because it's farthest from the original actor model. All of these implementations are relatively young, and I believe they will continue to evolve in interesting ways. If you have the freedom to try something new with the approach to your project, do your own assessment to determine if one of these solutions is a good option.

Alex Miller is a tech lead at Terracotta Inc, the maker of the open-source Java clustering product Terracotta. Previously, Alex worked at BEA Systems on the AquaLogic product line and was Chief Architect at MetaMatrix. Alex blogs at Pure Danger Tech, tweets as @puredanger and speaks at user groups, conferences, and the No Fluff Just Stuff tour. He has an enduring love for his wife and three children, music, and nachos.

Learn more about this topic

More from JavaWorld

Join the discussion
Be the first to comment on this article. Our Commenting Policies