J.U.C并发框架之AQS(三):ReentrantLock中断锁

本篇文章将研究ReentrantLock中断锁,首先提两个问题预热:中断锁触发的时机是什么时候?中断锁响应中断后,线程接下来做什么?

中断锁源码解析

和非中断锁不同的是,方法调用的是lockInterruptibly():

1
2
3
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}

中断一处

既然是中断锁,到底中断在哪里,我急切的问自己,继续追踪源码,发现产生中断的地方有这么两处,一处是在:

1
2
3
4
5
6
7
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}

写一个简单的例子验证一下,start两个线程Thread-0和Thread-1,这里需要说明的是,为了防止在执行过程中Thread-1的Thread.interrupted()过早执行而未抛出此处异常,使用了LockSupport.park()先将程序挂起:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
public class ReentrantLockDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new ThreadDemo01();
Thread t2 = new ThreadDemo01();
System.out.println(Thread.currentThread().getName());
t1.start();
t2.start();
Thread.sleep(3000);
t2.interrupt();
}
public static class ThreadDemo01 extends Thread {
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
if (Thread.currentThread().getName().equals("Thread-1")) {
LockSupport.park();
}
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(7000);
System.out.println("线程" + Thread.currentThread() + "执行完sleep方法");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}

输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
main
Thread-0
java.lang.InterruptedException
Thread-1
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.demo.ReentrantLockDemo$ThreadDemo01.run(ReentrantLockDemo.java:27)
线程Thread[Thread-0,5,main]执行完sleep方法
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
线程Thread[Thread-1,5,main]执行完sleep方法
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at com.demo.ReentrantLockDemo$ThreadDemo01.run(ReentrantLockDemo.java:39)
Process finished with exit code 0

AbstractQueuedSynchronizer.java:1220指的就是acquireInterruptibly方法抛出中断异常的语句。如果触发了这一处的异常,线程Thread-1将不再受AQS内部锁的约束,执行方法体到结束,后续还抛出了一个java.lang.IllegalMonitorStateException异常,这是执行了unlock语句的缘故。

中断二处

另一处产生中断的地方在doAcquireInterruptibly方法内,也就是说一旦处于等待队列的挂起线程被非unpark()方式唤醒,将直接抛出中断异常,例子如下:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public class ReentrantLockDemo {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new ThreadDemo01();
Thread t2 = new ThreadDemo01();
System.out.println(Thread.currentThread().getName());
t1.start();
t2.start();
Thread.sleep(3000);
t2.interrupt();
// LockSupport.unpark(t2);
}
public static class ThreadDemo01 extends Thread {
public static ReentrantLock lock = new ReentrantLock();
@Override
public void run() {
// if (Thread.currentThread().getName().equals("Thread-1")) {
// LockSupport.park();
// }
try {
lock.lockInterruptibly();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(7000);
System.out.println("线程" + Thread.currentThread() + "执行完sleep方法");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
}

输出为:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
main
Thread-0
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
Thread-1
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
at com.demo.ReentrantLockDemo$ThreadDemo01.run(ReentrantLockDemo.java:28)
线程Thread[Thread-0,5,main]执行完sleep方法
Exception in thread "Thread-1" java.lang.IllegalMonitorStateException
线程Thread[Thread-1,5,main]执行完sleep方法
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:151)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1261)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:457)
at com.demo.ReentrantLockDemo$ThreadDemo01.run(ReentrantLockDemo.java:40)
Process finished with exit code 0

和之前的例子输出不同,这里产生异常的地方在AbstractQueuedSynchronizer.java:898。如果要观察unpark()对程序的影响,可以将代码中注释部分LockSupport.unpark(t2);t2.interrupt();替换,此时线程并不会抛出中断异常。

小结

从前文的分析可知,中断锁的目的就是响应中断,可以通过中断机制避免长时间的线程等待。线程在尝试获取中断锁的时候,中断标志位非零会触发线程抛出中断异常,线程在等待队列挂起时,如果被中断,也会抛出中断异常。

调试

在线程上打断点要注意,因为这相当于阻塞线程,一旦一个线程阻塞,另一个竞争状态的线程会抢占锁,最终产生误导的结果。