本篇文章将研究ReentrantLock中断锁,首先提两个问题预热:中断锁触发的时机是什么时候?中断锁响应中断后,线程接下来做什么?
中断锁源码解析
和非中断锁不同的是,方法调用的是lockInterruptibly():
|
|
中断一处
既然是中断锁,到底中断在哪里,我急切的问自己,继续追踪源码,发现产生中断的地方有这么两处,一处是在:
|
|
写一个简单的例子验证一下,start两个线程Thread-0和Thread-1,这里需要说明的是,为了防止在执行过程中Thread-1的Thread.interrupted()过早执行而未抛出此处异常,使用了LockSupport.park()先将程序挂起:
|
|
输出为:
|
|
AbstractQueuedSynchronizer.java:1220
指的就是acquireInterruptibly方法抛出中断异常的语句。如果触发了这一处的异常,线程Thread-1将不再受AQS内部锁的约束,执行方法体到结束,后续还抛出了一个java.lang.IllegalMonitorStateException异常,这是执行了unlock语句的缘故。
中断二处
另一处产生中断的地方在doAcquireInterruptibly方法内,也就是说一旦处于等待队列的挂起线程被非unpark()方式唤醒,将直接抛出中断异常,例子如下:
|
|
输出为:
|
|
和之前的例子输出不同,这里产生异常的地方在AbstractQueuedSynchronizer.java:898
。如果要观察unpark()对程序的影响,可以将代码中注释部分LockSupport.unpark(t2);与t2.interrupt();替换,此时线程并不会抛出中断异常。
小结
从前文的分析可知,中断锁的目的就是响应中断,可以通过中断机制避免长时间的线程等待。线程在尝试获取中断锁的时候,中断标志位非零会触发线程抛出中断异常,线程在等待队列挂起时,如果被中断,也会抛出中断异常。
调试
在线程上打断点要注意,因为这相当于阻塞线程,一旦一个线程阻塞,另一个竞争状态的线程会抢占锁,最终产生误导的结果。