JavaOne Report: The MemoryModel and Concurrency Utilities

For me, the most interesting sessions of the conference were regarding the new MemoryModel and Concurrency Utilities. Having recently done quite a bit of threading code, it's clear that JDK 5.0 will vastly improve on the current primitives synchronized, wait and notify (which will, of course, still be supported in JDK 5.0, though they probably won't be used as much).

The new memory model will ensure that things will work much more intuitively than they currently do. The double-checked locking idiom for instance is broken right now due to allowed statement re-ordering and caching of variables in registers (this, by the way, is not just a Java-issue, it's also broken in languages like C++ and C#). Now statement reordering (either by the compiler or by the processor) and caching of values in registers are extremely important features for performance so they cannot simply be turned off to make stuff like this work. Using new definitions for volatile and synchronized, stating when they should read/write to memory (as opposed to using cached values), things will work much better and more intuitively in JDK 5.0.

The new concurrency utilities package (based on Doug Lea's threading libraries) contains a lot of new Classes and Interfaces to make writing multi-threaded code much easier. The idea behind this package is to do for threading what the collections framework did for collections. When they first mentioned this, I was rather skeptical to say the least. At the end of the session though, I believed they might just pull it off.

The concurrent utilities package will implement thread pooling through Executors. The Executor interface consists of a single "void execute(Runnable command)" method, so is effectively just "something that runs Runnables". This can be a single threaded worker, a regular thread pool, a scheduled thread pool or even a custom implementation. There's an Executors class which has factory methods to create many commonly used types of Executors. The actual type returned by these factory methods is typically an "ExecutorService": a sub-interface of Executor with added methods to, among other things, manage termination of the pool.

JDK 5 also introduces concurrent collections. These collections achieve thread safety while still allowing certain operations to occur concurrently. While using the synchronized keyword often requires placing a lock on the entire collection during iteration, concurrent collections will allow multiple operations to overlap each other. The new ConcurrentHashMap class for instance allows overlap of multiple reads, reads over writes and even up to 15 overlapping writes (it will be interesting to check out the code to see how they pulled off the concurrent writes). Also, iteration over a ConcurrentHashMap will never throw a ConcurrentModificationException.

Another example of a concurrent collection is the CopyOnWriteArrayList - this class is optimized for cases where iteration is much more frequent than insertion or removal. It is for instance ideal for EventListeners. As the name suggests, every write causes the current collection data to be copied, thus guaranteeing that no ConcurrentModificationExceptions will ever be thrown during iteration - if a collection gets changed while another thread is iterating, that other thread will continue seeing the original (non-changed) data.

There are also a number of lower level concurrency utils like Locks and Conditions that will be introduced in JDK 5.0. A Lock allows for functionality similar to that provided by the synchronized keyword, but without forcing you to lock and unlock within a single block of code. Locks also have added options like the ability to check a Lock without waiting infinitely, or timing out Locks.

The ReentrantLock class implements a reentrant mutual exclusion lock with the same semantics as built-in monitor locks (synchronized), but with extra features like the ability to interrupt a thread waiting to acquire a lock, specifying a timeout while waiting for a lock, polling for lock availability, and support for multiple wait-sets per lock via the "Condition" interface. ReentrantLock outperforms built-in monitor locks in most cases, but is slightly less convenient to use as it requires a finally block to release lock, like

Lock lock = new ReentrantLock();
...
lock.lock();
try {
    ...
} finally {
    lock.unlock();
}

A Condition is the abstraction of wait-notify. You can get a Condition object from an existing Lock object, and subsequently call await() and signal() on them. This allows you to for instance have multiple Condition objects against a single Lock, instead of having to use notifyAll to wake up all threads, only to put all but one of them back into a wait-state.

There are many other useful classes and interfaces like ReadWriteLock, ReentrantReadWriteLock, Semaphore, CountdownLatch, etc.

And last but not least, JDK 5.0 will introduce Atomic Variables: classes that support atomic operations like "compare-and-set" and "get,set-and-arithmetic". At runtime, the JVM will use the best available implementation of this functionality, depending on the platform it runs on. So this may internally be implemented using a lock, or may use a native contruct (if supported by the processor) to do these kinds of atomic operations. An example of an atomic variable class is "AtomicInteger". which will be useful for things like counters and sequence numbers.

TrackBack URL for this entry: http://www.hutteman.com/scgi-bin/mt/mt-tb.cgi/162
Comments

Brad Adams had an interesting blog entry on double locking: http://blogs.msdn.com/brada/archive/2004/05/12/130935.aspx

I thought double locking wasn't broken in C++: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Posted by Matt at July 4, 2004 8:16 AM

Matt: Interesting links: thanks.

The memory model session was presented by Bill Pugh (I'd link to the session itself, but Sun seems to have done some weird stuff where, unless you're logged in, those links won't work - the session abstract is just one click away from his Bio page though), who is one of the authors of the article at the second link you provided. He stated in that session that the Double Checked Locking Pattern (DCLP) was broken not only in Java (prior to the upcoming JDK 5), but also in C++, C# and other languages.

There's also an article in the current (July 2004) issue of Dr. Dobbs Journal on the problems that C++ has with the DCLP (link goes to table of contents; article itself is only available as part of their premium subscription). This article is part 1 of 2, there will be another one in next month's issue.

Posted by Luke Hutteman at July 4, 2004 11:44 AM

DCL is not broken in C#. See Vance Morrison's seminal post on the topic:
http://discuss.develop.com/archives/wa.exe?A2=ind0203B&L=DOTNET&P=R375

Posted by RichB at July 5, 2004 2:55 PM

Another interesting read - thanks for the link.

I'm not sure if I agree that DCL is not broken in C# though - in it's basic form, it IS broken. The author posts a solution of how to make it work though, being:


public static LazyInit fetch() {
  if (singleton == null) {   // quick check
    lock(typeof(LazyInit)) {
      if (singleton == null) {   // double check
        LazyInit newObj = new LazyInit();
        System.Threading.Thread.WriteMemoryBarrier();
        singleton = newObj;
      }
    }
  }
  return singleton;
}

But this is not an intuitive solution and I'm sure many developers unaware of the DCL problem will continue to implement the "naive" implementation without the WriteMemoryBarrier. In Java however (starting in JDK5), the simple DCL implementation WILL work, as long as the singleton is declared as volatile. I think that, by not having to deal with explicit MemoryBarriers, it should be a lot easier to write and understand multi-threaded code in Java as it will be in .NET.

Posted by Luke Hutteman at July 5, 2004 10:38 PM

oops - I see I had not read the article that thoroughly yet - I just saw he states at the end that for X86 processors, the DCL will work without the MemoryBarrier (which is actually a no-op there). Since .NET is not inherently multi-platform (conveniently ignoring the ECMA C# standard and the SSCLI for now), I guess it's not as big of an issue there as it with with a multi-platform environment like Java.

Posted by Luke Hutteman at July 5, 2004 10:50 PM

RSS is zo verschikkelijk cool!

Trackback from Jemimus' Blog at July 14, 2004 5:00 AM

RSS is zo verschikkelijk cool!

Trackback from Jemimus' Blog at July 14, 2004 9:42 AM

Comparing Blog sites

Trackback from Jemimus' Blog at July 15, 2004 3:15 AM

Actually, the DCL won't work even for X86 with it's strong memory model. The problem is that the COMPILER can validly mess up the ordering in ways permitted by the memory model that in-effect over-rides the X86 memory model. Both the compiler and the chip need to be considered in this model, and Vance only considered the one issue when saying is would work. It is possible that the compiler doesn't perform such modifications, but the write memory barrier call is two-fold: both an instruction to the compiler as well as a runtime instruction. The runtime is a no-op for X86, but it is NOT a no-op for the compiler even on that platform.

Posted by Bill Wallace at December 22, 2005 2:46 PM
This discussion has been closed. If you wish to contact me about this post, you can do so by email.