Java – How to avoid IllegalMonitorStateException when using lockInterruptibly on Reentrantlock

concurrencyjava

I want to replace a syncronized block with a ReentrantLock to support interruption of waiting for the lock. For this, I use the lockInterruptibly() method and the idiomatic try/finally block:

private ReentrantLock lock = new ReentrantLock();

try
{
  lock.lockInterruptably();
}
catch( InterruptedException e )
{
  Thread.currentThread.interrupt();
}
finally
{
  lock.unlock();
}

The problem is that the finally ofcourse also happens when the InterruptedException happens. This results in an IllegalMonitorStateException, because the lock is not held by the current thread.

This simple program proves this:

public class LockTest
{
public static void main( String[] args )
{
    System.out.println("START");

    Thread interruptThread = new Thread( new MyRunnable( Thread.currentThread() ) );
    interruptThread.start();
    ReentrantLock lock = new ReentrantLock();

    Thread takeLockThread = new Thread( new TakeLockRunnable( lock ) );
    takeLockThread.start();

    try
    {
        Thread.sleep( 500 );
        System.out.println("Trying to take lock on thread " + Thread.currentThread().getName());
        lock.lockInterruptibly();
    }
    catch (InterruptedException e)
    {
        e.printStackTrace();
    }
    finally {
        lock.unlock();
    }

    System.out.println( "DONE");
}

private static class MyRunnable implements Runnable
{
    private Thread m_thread;

    private MyRunnable( Thread thread)
    {
        m_thread = thread;
    }

    @Override
    public void run()
    {
        try
        {
            Thread.sleep( 1000 );
        }
        catch (InterruptedException e)
        {
            // ignore
        }
        System.out.println( "Interrupting thread " + m_thread.getName() );
        m_thread.interrupt();
    }
}

private static class TakeLockRunnable implements Runnable
{
    private ReentrantLock m_lock;

    public TakeLockRunnable( ReentrantLock lock )
    {
        m_lock = lock;
    }

    @Override
    public void run()
    {
        try
        {
            System.out.println("Taking lock on thread " + Thread.currentThread().getName());
            m_lock.lock();
            Thread.sleep( 20000 );
        }
        catch (Exception e)
        {
            e.printStackTrace();
        }
        finally {
            m_lock.unlock();
        }
    }
}
}

It prints this output:

START
Taking lock on thread Thread-1
Trying to take lock on thread main
java.lang.InterruptedException
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:877)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1201)
    at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:312)
    at LockTest.main(LockTest.java:25)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Exception in thread "main" java.lang.IllegalMonitorStateException
    at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:127)
    at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1239)
    at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:431)
    at LockTest.main(LockTest.java:32)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
    at java.lang.reflect.Method.invoke(Method.java:597)
    at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Interrupting thread main

Any idea on what the best way is to avoid this?

Best Answer

the lockInterruptibly() call should be outside the finally block. note, this is always try for use the Lock API (whether you use lock() or lockInterruptibly()), as you don't want to do the "unlock" work unless you have acquired the lock.

try {
  lock.lockInterruptibly();
  try {
    // do locked work here
  } finally {
    lock.unlock();
  }
} catch( InterruptedException e ) {
  Thread.currentThread.interrupt();
}