Home  Synchronization and concurrency  wait/notify  final  volatile  synchronized keyword  Java threading  Deadlock (and avoiding it)  Java 5: ConcurrentHashMap  Atomic variables  Explicit locks  Queues  Semaphores  CountDownLatch  CyclicBarrier

Explicit locks in Java 5

Before Java 5, the language offered two key synchronization facilities: the synchronized keyword allows exclusive locking at the level of a block or method, and in tandem with this locking mechanism, the wait/notify idiom effectively allows threads to wait, interruptably, for a 'signal' from another thread. The standard form of locking provided by the synchronized keyword has limitations:

  • Once a thread decides to enter a synchronized section, it commits to potentially waiting forever for the lock to become available. In particular, this means that cases where we need to hold more than one lock to perform our operation are difficult. Because synchronized is an all-or-nothing operation, we can't, for example, define a "back-off policy" if one lock is quickly available but the other isn't.
  • Only one thread can hold the lock at once: there's no facility, for example, to allow multiple threads holding a lock simultaneously for read-only access;
  • Locking with synchronized happens purely at the block level: one block cannot acquire a lock which is then released in another block (this functionality is sometimes useful for traversing structures consisting of linked nodes, where we want to lock only the node(s) we're currently accessing, and effectively 'pass on the lock' as we traverse the structure).
  • The fact that a block is synchronized is essentially determined at compile time. At runtime, there's no good way to say "only synchronized if X" (e.g. "only synchronize on the database connection if it's the shared one, not a thread-local one")1.
  • From our Java program, we can't get any "metadata" about the lock: that is performance information such as how many threads are currently waiting on the lock, on average how long it takes to acquire the lock etc. (Depending on your VM, there may be roundabout methods to get such information from outside the program, with varying degrees of difficulty.)

Of course, it does have the advantage of simple syntax and built-in support from the JVM (people who have stuck to good-old synchronized have seen their tenacity rewarded with gradual performance improvements over the course of several JVM updates). If you need some of the features of the above list, you can 'roll your own' lock with judicious use of wait/notify (for example, some of the Swing text editing code actually uses an implementation of a read/write lock). So we could create a Lock class with the following functionality:

public class Lock {
  /**
   * Waits for up to 'maxWait' milliseconds to acquire the lock,
   * and returns true if the lock was acquired.
   **/
  public boolean acquireLock(int maxWait)
    throws InterruptedException { ... }

  public void releaseLock() { ... }
}

(By the way, if you're wondering how to actually implement the lock we've just described, then see this rough implementation of a lock pre-Java 5.) Such a lock would then be used by the caller as follows:

Lock lck = getDatabaseLock();
if (!lck.acquireLock(2000)) {
  throw new RuntimeException("Couldn't get DB lock in 2 seconds");
}
try {
  ... do interesting thing with database ...
} finally {
  lck.releaseLock();
}

One disadvantage of this locking mechanism (apart from any performance issues2) is the syntax: we have to explicitly deal with lock objects and in particular, we must ensure that we release the lock in a finally clause once we've acquired it. With a regular synchronized block, the compiler takes care of this automatically for us.

But on the plus side, this lock solves some of the problems mentioned above. In particular, it won't wait forever if the lock can't be acquired for some reason. And once our lock is implemented in Java, we can do other interesting things with it, such as build in performance monitoring.

Locks in Java 5

Well, all this introduction wasn't intended to make you run away and write a new lock class. Java 5 already introduces classes with functionality similar to the example Lock class above. But they call on the new atomic variable access facilities of Java 5 to create more efficient, scalable versions than were previously possible. They are also fuller implementations, dealing with some issues (in particular fairness) which we don't attempt to deal with in our simple example.

So, on the next page, we look at explicit lock classes in Java 5.


1. There is a bad way: you could set which object gets synchronized on at runtime, and to mean "don't synchronize", you can synchronize on new Object().
2. For what it's worth, the simple implementation I give performs about two times worse than regular Java synchronization. That is, in a simple test of contended array access, we get about half as much throughput synchronizing using this lock implementation as opposed to regular synchronized lock. Contrast this with Java 5's Lock implementations which generally offer better (or at least, no worse) throughput than synchronized and still offer interruptability. Of course, if you really need interruptable locking and you really can't move to Java 5, half the throughput may be a price worth paying, and this example can doubtless be improved a little.

Article written by Neil Coffey (@BitterCoffey).

Software

 LetterMeister (word puzzle game for iPhone)
 Currency Quoter (currency converter/predictor)
 French Vocab Games for iPhone/iPad
 Vocabularium: create Spanish vocab podcasts


Java programming articles and tutorials on this site are written by Neil Coffey (@BitterCoffey). Suggestions are always welcome if you wish to suggest topics for Java tutorials or programming articles, or if you simply have a programming question that you would like to see answered on this site. Most topics will be considered. But in particular, the site aims to provide tutorials and information on topics that aren't well covered elsewhere, or on Java performance information that is poorly described or understood. Suggestions may be made via the Javamex blog (see the site's front page for details).
Copyright © Neil Coffey 2014. All rights reserved.