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 (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?InterruptibleNon-interruptible
ForeverlockInterruptibly()lock()
With max timetryLock(time, TimeUnit)No method provided, but if we really wanted, we could re-call tryLock() if interrupted, until the time ran out.
NotryLock()
(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.

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 2015. All rights reserved.