Explicit locks in Java 5 (ctd)
On the previous page, we introduced the
Java 5 Lock interface
and its implementation
ReentrantLock.
We can use it to effectively perform a similar function to the synchronized
keyword, acquiring the lock and then releasing the lock in a finally clause. Of course,
if that were the only function of a Lock, it wouldn't be terribly useful.
The big advantage comes with some other features which we'll explore here.
Timed and interruptible locking
The key advantage of Lock classes is that offers alternatives to the
blocking lock() method. We can call tryLock(), passing in
the maximum time to wait for the lock before giving up:
public void doInterestingThingWithDatabase()
throws DatabaseBusyException, InterruptedException {
Lock lck = getDatabaseLock();
if (!lck.tryLock(2, TimeUnit.SECONDS)) {
throw new DatabaseBusyException();
}
try {
... do something interesting ...
} finally {
lck.unlock();
}
}
The TimeUnit class was also introduced in Java 5 and makes specifying
delays clearer in methods such as this. (For example, if we want seconds, we don't have
to convert to milliseconds ourselves.) When a thread is waiting for a lock with a
time limit as here, it can be interrupted, and tryLock() throws
InterruptedException. As here, the most appropriate thing to do with
such an exception is often to simply throw it up the stack.
If we don't actually want to wait, but just want
to abandon our operation if the lock is not available immediately, we can use tryLock()
in its no-parameter version. (Since it's instantaneous, this
method doesn't throw InterruptedException.)
if (!lck.tryLock()) {
throw new RuntimeException("Resource in use");
}
...
Finally, there is a means of making a thread wait potentially forever for the lock, but
still allowing it to be interruptible. The method lockInterruptibly
provides this functionality. Here is a summary of the lock methods:
Wait? | Interruptible | Non-interruptible |
Forever | lockInterruptibly() | lock() |
With max time | tryLock(time, TimeUnit) | No method provided, but if we really wanted, we could re-call tryLock() if interrupted, until the time ran out. |
No | tryLock() (If we're not waiting, the notion of interruptibility doesn't apply!) |
Locking methods provided by the Lock interface
Lock fairness
With locking via synchronized blocks, if there are multiple threads waiting
for a lock, it is up to the JVM to decide which thread gets the lock first when the
owning thread releases it. In principle, there are different policies possible,
but the most efficient (and thus the one implemented by most JVMs, I suspect)
is to make the lock "unfair" and allow "barging". That is: even if several threads are waiting, if
another thread "barges in" just as the lock has been released, then it can acquire
the lock ahead of the other waiting threads.
Such "unfair" behaviour is also the default behaviour of ReentrantLock.
In terms of throughput, it actually makes sense: if a thread comes along at the right time,
it's generally better to let it continue than force it to be suspended just because
another thread, already suspended at this point, has been waiting for the lock. If we let the
thread "barge in" it can usually get in and out of the lock
before the next interrupt (i.e. when another waiter would have been able to be switched in).
So although a thread has "jumped the queue", overall,
more threads get to acquire the lock in a given time.
When instantiating ReentrantLock, it is possible to specify its policy
as "fair". In other words, when the lock is released, waiters will acquire it in strict
first-come-first-served order. In this mode, it is possible for a thread to have to
wait for the lock even though at that precise moment it is not owned by another thread
(so that it can go to a "more worthy contender" currently waiting for it).
In some real-time applications it may be worth paying the
throughput penalty to try and ensure some kind of maximum delay on lock acquisition
by any one thread. But usually lock fairness is unnecessary and the
penalty is not worth paying.
Read/write locks
On the next page, we continue our discussion with a look at
read/write locks in Java 5.
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.