前言
使用ReentrantLock过程中遇到IllegalMonitorStateException
崩溃,于是研究一番。
复现
使用如下代码可以复现:
public class LockTest {
public static void test() {
Thread thread = new Thread(new TestRunnable());
thread.start();
// 加上sleep代码不会IllegalMonitorStateException
// try {
// Thread.sleep(1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
thread.interrupt();
}
private static class TestRunnable implements Runnable {
private static Lock lock = new ReentrantLock();
@Override
public void run() {
try {
// lock.lock(); // 没问题
lock.lockInterruptibly(); // 抛出IllegalMonitorStateException
try {
Thread.sleep(300);
} catch (InterruptedException e) {
// log
}
} catch (InterruptedException e) {
// log
} finally {
lock.unlock();
}
}
}
}
源码分析
public class ReentrantLock implements Lock, java.io.Serializable {
abstract static class Sync extends AbstractQueuedSynchronizer {
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
static final class NonfairSync extends Sync {
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException(); // 抛出异常,getExclusiveOwnerThread为null
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
}
// 调用lock方法会去获取锁,直接获取或者在AQS里排队获取,不会判断线程状态
public void lock() {
sync.lock();
}
// 判断线程状态后再获取锁
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
public void unlock() {
sync.release(1);
}
}
从源码可以看出
- lock()方法会去获取锁,直接获取或者在AQS里排队获取,不会判断线程状态
- lockInterruptibly() 判断线程状态后再获取锁
崩溃原因
导致崩溃的原因是调用thread.interrupt()
太快了,ReentrantLock还在调用acquireInterruptibly
时候该线程就已经interrupted了。判断Thread.interrupted()
直接抛出InterruptedException。
然后finally语句在调用unlock释放锁时,exclusiveOwnerThread为null,因为在走进获取锁的流程前该线程已经interrupted了。
将测试代码中test方法加上sleep代码则不会IllegalMonitorStateException,因为那时候已经获取到锁了,exclusiveOwnerThread不为null。
结论
如测试代码所示,该代码并没有线程竞争,只有一个线程也会有这种问题存在,更何况在多线程场景下使用;
所以更需要注意不要滥用lock.lockInterruptibly,尤其有thread.interrupt()
调用情况下。
不过一般等待唤醒场景下都会添加thread.interrupt()以中断线程,以尽早结束线程,所以尽量避免使用lock.lockInterruptibly
。
因为synchronized
不能被中断,使用了ReentrantLock了就意味着要调用thread.interrupt()
,所以还是尽量不使用它吧;只是多获取一次锁,在后面的condition判断中还是会被中断,所以只能说问题不大吧,使用lock()就完事了。