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:

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). But there is another approach: we could perform synchronization via explicit locks.

What is an explicit lock?

When you use the synchronize word, what happens— and what is to some extent hidden from view of the programmer— is that the thread must acquire a lock on the object being synchronized on before entering the synchronized block, and then release that lock when it exits the block. So the following:

synchronized (someObject) {
  // do some stuff
}

is in effect a syntactic shorthand for something like this:

someObject.lock.acquire();
try {
  // do some stuff
} finally {
  someObject.lock.release();
}

Each lock can be "owned" by up to one thread at any time. When a thread tries to acquire ownership of the lock, it must wait if some other thread already has ownership. Once the owning thread releases the lock, if there are any threads sitting waiting to acquire it, one of them will then be able to proceed.

Now in fact, the lock in question— often called a monitor— is a hidden construct inside the JVM, and not something we have access to from our Java program. So if we use synchronized, we must accept the behaviour mentioned above. But there's nothing particularly magical about the built-in synchronization locks (other than they're "built in" and have been tuned for good performance under "typical" conditions): so long as we can create some Java object that has the acquire/release functionality mentioned, and make it perform2 adequately, then we could use that instead, and actually code our synchronized "explicitly", much like the second code fragment above. This becomes interesting when we consider variants of our lock, such as the following, where we try for two seconds to acquire the lock, but then "back off" after that time, thus preventing deadlock:

if (!lock.acquireWithTimeout(2000L)) {
  throw new RuntimeException("Couldn't get lock");
}
try {
  // do some stuff
} finally {
  lock.release();
}

Explicit locks in Java 5

With judicious use of wait/notify, it is actually possible to construct a lock object pre-Java 5. But Java 5 and the concurrency packages provide two key characteristics that make explicit locks more viable:


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. Lock performane is something we look at in more detail later, but encapsulates ideas you might expect: we want to minimise the typical and worst-case time needed to acquire the lock; we want to minimise "dead time" between a lock becoming available and the next waiter acquiring it; and irrespective of "raw" times, we may want the lock to scale in a particular way as the number of contending threads increases.


If you enjoy this Java programming article, please share with friends and colleagues. Follow the author on Twitter for the latest news and rants.

Editorial page content written by Neil Coffey. Copyright © Javamex UK 2021. All rights reserved.