David Dice's Weblog

Thursday Aug 31, 2006

java.util.concurrent ReentrantLock vs synchronized() - which should you use?

In J2SE 6 there's little difference - either should suffice in most circumstances. ReentrantLock might be more convenient if you need to implement a hand-over-hand locking protocol. A classic example of hand-over-hand locking would be a thread that traverses a linked list, locking the next node and then unlocking the current node. That's hard to express with Java's lexically balanced synchronized construct. Synchronized, however, is a mature and first-class language feature.

With respect to performance, both mechanisms are up to the task. (It's worth noting that the synchronization primitives in modern JVMs provide latency and throughput performance that is typically better than that of the native pthreads_mutex constructs). The builtin synchronized construct currently has a few advantages, such as lock coarsening, biased locking (see below) and the potential for lock elision via escape analysis. Those optimizations aren't currently implemented for ReentrantLock and friends. There's no fundamental reason, however, why a JVM couldn't eventually apply such optimizations to ReentrantLock.

The Synchronized implementation also provides adaptive spinning, whereas ReentantLock currently does not. Adaptive spinning employs a two-phase spin-then-block strategy. Briefly, on multiprocessor systems a contended synchronized enter attempt will spin briefly before blocking in order to avoid context switching. Context switching is wasted work -- it doesn't contribute toward forward progress of the application. Worse, it causes TLBs and caches to be repopulated when the blocked thread eventually resumes. (This is the so-called "cache reload transient"). The spin duration varies as a function of the success/failure ratio of recent spin attempts on that same monitor, so the mechanism adapts automatically to parallelism, current system load, application modality, critical section length, etc. In addition, we avoid spinning for a lock where the current lock owner is itself blocked and unlikely to release the lock in a timely fashion. On solaris our checks can be more refined, determining if the target thread is ONPROC (running), for instance, via the contract private thr_schedctl interface. And it should go without saying that we spin "politely", using a backoff to avoid generating excessive and wasteful traffic on the coherency bus, as well as using PAUSE on IA32 and AMD64 platforms. We'll likely add spinning support to ReentrantLock in a future release.

If you're curious about the inner workings or ReentrantLock, see Doug Lea's The java.util.concurrent Synchronizer Framework. If you're curious about adaptive spinning see synchronizer.cpp in the J2SE 6 source kit.

Finally, ReentrantLock and synchronized are equivalent with respect to the clarified Java Memory Model (JMM, JSR133).

Comments:

What about LockSupport.park() and LockSupport.unpark(<thread>) on Linux and Windows? There seem to be periodical latencies associated with unpark calls. Do other, more intelligent locking classes, such as locks, use park/unpark behind the sciences or not? Thank you

Posted by Alex on May 15, 2007 at 04:18 PM EDT #

Hello Alex, All the JSR166 j.u.c operators that might need to block or wake threads are based on LockSupport.park() and unpark(). As for synchronized, it also uses an internal form of park-unpark at the lowest levels. I'm curious about your comment regarding periodical latencies. Could you provide more details? Regards Dave

Posted by Dave on May 30, 2007 at 09:07 AM EDT #

Post a Comment:
  • HTML Syntax: NOT allowed
Copyright © 2006-2008 Dave Dice, All Rights Reserved.

Calendar

Feeds

Search

Links

Navigation

Referers

XML Feed
Creative Commons License
This work is licensed under a Creative Commons Attribution-No Derivative Works 3.0 License.