Reentrant lock and deadlock with Java

A reentrant locking mechanism allows the thread holding the lock to re-enter a critical section. This means that you can do something like this:

public synchronized void functionOne() {

    // do something

    functionTwo();

    // do something else

    // redundant, but permitted...
    synchronized(this) {
        // do more stuff
    }    
}

public synchronized void functionTwo() {
     // do even more stuff!
}

In a non-reentrant lock, you would have a deadlock situation when you try to call functionTwo() from functionOne() because the thread would have to wait for the lock...which it holds itself.

Deadlock, of course, is the evil situation in which Thread 1 holds lock A and is waiting for lock B while Thread 2 holds lock B and is waiting for lock A. Thus, neither can continue. This code sample creates a deadlock:

public synchronized void deadlock() throws InterruptedException {
    Thread th = new Thread() {
        public void run() {
            deadlock();
        }
    }.start();

    th.join();
}

The calling thread tries to wait around for the spawned thread, which in turn can't call deadlock() until the caller has exited. Ka-boom!


A deadlock occurs when a thread waits for a condition which will never become true.

The obvious case is when you are trying to lock two locks, locked in a different order by different threads.

ReentrantLock lock1 = new ReentrantLock();
ReentrantLock lock2 = new ReentrantLock();

public void methodA() {
    lock1.lock();
    lock2.lock();
    // do something and unlock both.
}

public void methodB() {
    lock2.lock();
    lock1.lock();
    // do something and unlock both.
}

As you can see it is possible for a thread to call methodA and obtain lock1 waiting for lock2, and another thread to call methodB and obtain lock2 waiting for lock1.


However, it's possible for a thread to deadlock itself. An example is ReentrantReadWriteLock because it doesn't support upgrading a read lock to a write lock.

ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
rwl.readLock().lock();
// do we need to update?
rwl.writeLock().lock(); // will wait for the readLock() to be released!

An obscure opportunity to deadlock yourself is when implied locks are used. A static initialiser block is implicitly thread-safe so a lock is used even though static initialiser blocks are not synchronized

class A {
     private static int VALUE;
     static {
        Thread t = new Thread() {
            public void run() {
                // waits for the A class to load.
                VALUE = someLongTask();
            }
        };
        t.start();
        // waits for the thread.
        t.join();
    }
}

Again you have a deadlock!