Java 在Reentrantlock上以可中断方式使用Lock时如何避免非法MonitorStateException

Java 在Reentrantlock上以可中断方式使用Lock时如何避免非法MonitorStateException,java,concurrency,Java,Concurrency,我想用可重入锁定替换同步化的块,以支持中断等待锁定。为此,我使用了lockinterruptbly()方法和惯用的try/finally块: private ReentrantLock lock = new ReentrantLock(); try { lock.lockInterruptably(); } catch( InterruptedException e ) { Thread.currentThread.interrupt(); } finally { lock.unl

我想用
可重入锁定
替换
同步化的
块,以支持中断等待锁定。为此,我使用了
lockinterruptbly()
方法和惯用的try/finally块:

private ReentrantLock lock = new ReentrantLock();

try
{
  lock.lockInterruptably();
}
catch( InterruptedException e )
{
  Thread.currentThread.interrupt();
}
finally
{
  lock.unlock();
}
问题是,当中断异常发生时,当然也会发生finally。这将导致一个
非法监视器状态异常
,因为锁不由当前线程持有

这个简单的程序证明了这一点:

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();
        }
    }
}
}
它打印此输出:

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 开始 在线程1上取锁 试图锁定主线程 java.lang.InterruptedException 在java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:877)中 位于java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1201) 位于java.util.concurrent.locks.ReentrantLock.lockInterruptablely(ReentrantLock.java:312) 在LockTest.main(LockTest.java:25) 在sun.reflect.NativeMethodAccessorImpl.invoke0(本机方法)处 位于sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 在sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)中 位于java.lang.reflect.Method.invoke(Method.java:597) 位于com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) 线程“main”java.lang.IllegalMonitorStateException中的异常 位于java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:127) 位于java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1239) 位于java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:431) 在LockTest.main(LockTest.java:32) 在sun.reflect.NativeMethodAccessorImpl.invoke0(本机方法)处 位于sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 在sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)中 位于java.lang.reflect.Method.invoke(Method.java:597) 位于com.intellij.rt.execution.application.AppMain.main(AppMain.java:120) 中断主线程
关于避免这种情况的最佳方法,您有什么想法吗?

简单地使用布尔标志应该注意:

private ReentrantLock lock = new ReentrantLock();

boolean lockAcquired = false;

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

lockinterruptbly()
调用应该在finally块之外。注意,这总是尝试使用
Lock
API(无论您是使用
Lock()
还是
lockInterruptibly()
),因为除非您获得了锁,否则您不想执行“解锁”工作

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

我第一次使用的是
isHeldByCurrentThread
,但在阅读了所有评论之后,我认为这个版本是唯一真正正确的版本。@WimDeblauwe您指的是哪些评论,为什么您认为使用
isHeldByCurrentThread
不正确?@RobinGreen因为已经一年多了,我记不清了,但我觉得这个问题过去有更多的答案。无论如何,这个答案中给出了正确的方法,因此这是最后最重要的。@RobinGreen-有一个已删除的答案,它试图使用
isHeldByCurrentThread
来确定是否应该释放锁,但是如果锁被重新持有,它将无法正常工作。