Newsletter sign-up
View all newsletters

Enterprise Java Newsletter
Stay up to date on the latest tutorials and Java community news posted on JavaWorld

Sponsored Links

Optimize with a SATA RAID Storage Solution
Range of capacities as low as $1250 per TB. Ideal if you currently rely on servers/disks/JBODs

Understanding actor concurrency, Part 1: Actors in Erlang

A new way to think about structuring concurrent applications

  • Print
  • Feedback

Page 2 of 6

Shared-state concurrency

The most popular languages today rely on mutable shared state and locks as a model of concurrency. A typical example of multithreaded code is the Counter class in Listing 1.

Listing 1. A typical synchronized Counter class in Java

public class Counter {
  private int count = 0;

  public synchronized int increment() {
    return ++count;
  }
}

Counter is a thread-safe class that maintains a counter (the shared state) and protects access to that state by allowing only one thread at a time to read or modify that state. In Listing 1 that protection is provided by the synchronized block, but it could also be provided by explicit Lock objects.

If you've written programs with shared-state concurrency, you've likely encountered the legions of dragons that can arise. If mutable state is not modified and read under the scope of a lock, you might not see the latest version of the value from another thread. Or threads can obtain multiple locks in differing orders, leading to deadlock. Or the necessity for locking can lead to lock contention.

Perhaps the most difficult aspect of concurrent programming is that it's quite easy to combine several properly written concurrent components and thereby produce a system that either is broken or performs poorly. The composite system can be broken if the independent components modify state under different independent locks but the program requires atomic operations that modify state in both components. You can address this problem in a variety of ways with careful architecture, by exposing the locking semantics of the components, or by adding new layers of locks over the components. However, most of the easiest ways to deal with these issues result in poor performance or unmaintainable code.

Whether shared-state concurrency can scale is an equally important question. Will it scale to 16 cores? 32? 1000? Some parts of your program will naturally scale up. If you've got a thread pool working on a set of parallel tasks, it's entirely feasible to scale that to a larger number of cores. But in general, much of your program does not consist of discrete tasks that can be easily made parallel. Amdahl's Law is commonly used to calculate the maximum benefit from increasing the parallel parts of your program. But the sad fact remains that as you scale, the non-parallel parts of your program are the ones that matter, and you can't parallelize them away.

Actor concurrency

So what's the alternative? The actor model of concurrency has recently gained prominence, largely thanks to some success achieved by its flagship language, Erlang.

The actor model consists of a few key principles:

  • No shared state
  • Lightweight processes
  • Asynchronous message-passing
  • Mailboxes to buffer incoming messages
  • Mailbox processing with pattern matching

Let's look at these principles in more detail. An actor is a process that executes a function. Here a process is a lightweight user-space thread (not to be confused with a typical heavyweight operating-system process). Actors never share state and thus never need to compete for locks for access to shared data. Instead, actors share data by sending messages that are immutable. Immutable data cannot be modified, so reads do not require a lock.

  • Print
  • Feedback

Resources

More from JavaWorld