问题起源:在看可重入锁的时候无意中阅读到一句话“synchronized与Lock在默认情况下是不会响应中断(interrupt)操作,会继续执行完。lockInterruptibly()提供了可中断锁来解决此问题”
本文作用:将使用代码验证这样一种说法,并从源码的角度来分析为什么。
先看三段大同小异的代码
代码一:可证lockInterruptibly()能响应interrupt操作
public class InterruptionTest {
static Lock lock = new ReentrantLock();
static class innerClass implements Runnable{
Lock lock;
public innerClass(Lock lock) {
this.lock = lock;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is running");
lock.lockInterruptibly();
System.out.println(4);
System.out.println(Thread.currentThread().getName() + " is over");
} catch (Exception e) {
System.out.println("haha");
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
public static void main(String[] args) {
innerClass innerClass = new innerClass(lock);
Thread thread = new Thread(innerClass);
try {
lock.lock();
thread.start();
System.out.println(1);
TimeUnit.SECONDS.sleep(3);
thread.interrupt();
System.out.println(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
結果:
1
Thread-0 is running
java.lang.InterruptedException
at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:896)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1221)
at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:340)
at com.liuen.threads.InterruptionTest$innerClass.run(InterruptionTest.java:27)
at java.lang.Thread.run(Thread.java:745)
Exception in thread "Thread-0" java.lang.IllegalMonitorStateException
at java.util.concurrent.locks.ReentrantLock$Sync.tryRelease(ReentrantLock.java:155)
at java.util.concurrent.locks.AbstractQueuedSynchronizer.release(AbstractQueuedSynchronizer.java:1260)
at java.util.concurrent.locks.ReentrantLock.unlock(ReentrantLock.java:460)
at com.liuen.threads.InterruptionTest$innerClass.run(InterruptionTest.java:34)
at java.lang.Thread.run(Thread.java:745)
2
haha
代码二:可证lock()不能被interrupt
将代码一种加粗代码更换为:lock.lock()
结果:
1
Thread-0 is running
2
4
Thread-0 is over
代码三:可证synchronized同样不响应interrupt代码
public class InterruptionTest {
static class innerClass implements Runnable{
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + " is running");
synchronized (this){
System.out.println(4);
System.out.println(Thread.currentThread().getName() + " is over");
}
} catch (Exception e) {
System.out.println("haha");
e.printStackTrace();
}
}
}
public static void main(String[] args) {
innerClass innerClass = new innerClass();
Thread thread = new Thread(innerClass);
try {
synchronized (innerClass){
thread.start();
System.out.println(1);
TimeUnit.SECONDS.sleep(3);
thread.interrupt();
System.out.println(2);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
结果:
1
Thread-0 is running
2
4
Thread-0 is over
以上三段代码确实证实了“synchronized与Lock在默认情况下是不会响应中断(interrupt)操作,会继续执行完。lockInterruptibly()提供了可中断锁来解决此问题 ”
接下来我们来分析一下lockInterruptibly()的几段源码
DOC中的原文:
If the lock is not available then the current thread becomes disabled for thread scheduling purposes and lies dormant until one of two things happens:
- The lock is acquired by the current thread; or
- Some other thread interrupts the current thread, and interruption of lock acquisition is supported //其他线程打断了当前线程,并且当前是支持中断锁的
package java.util.concurrent.locks;
//lock接口中的lockInterruptibly() 抛出了打断异常
void lockInterruptibly() throws InterruptedException;
这个方法被多个类实现了,这里选取ReentrantLock里面的实现来分析
package java.util.concurrent.locks;
//sync是ReentrantLock内部类Sync的实例,继承自AQS
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1); //虽然Sync重写了AQS的部分方法,但是acquireInterruptibly()并没有被重写,而是直接用了AQS中的代码
}
接下来就可以看见AQS中这段代码的庐山真面目了
public final void acquireInterruptibly(int arg) //acquireInterruptibly 这个方法其实有必要着重说一下,可以看看AQS对acquire的源码,其目的就是为了获取锁
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException(); //立马看当前线程是否被打断,如果被打断了则直接抛出异常。这两行代码在lock()方法中是没有的
if (!tryAcquire(arg)) //这个tryAcquire(arg)方法是AQS中最重要的方法,各个锁在实现不同策略是基本都需要重写此方法。因为分析的ReentrantLock,在ReentrantLock中,其内部
doAcquireInterruptibly(arg); //在实现公平锁和非公平锁的时候都重写了这个方法。这里主要看一下后面doAcquireInterruptibly()
}
doAcquireInterruptibly( ) 这个方法在AQS内部,并且并没有被任何子类重写。“没有被任何子类重写”这点很重要,
因为要知道AQS的doc描述:
Provides aframework for implementing blocking locks and related synchronizers (semaphores, events, etc) that rely on first-in-first-out (FIFO) wait queues
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt()) //这里shouldParkAfterFailedAcquires用来判断是否阻塞当前节点,parkAndCheckInterrupt来执行阻塞,下面一段代码会贴出parkAndCheckInterrupt
throw new InterruptedException(); //如果看过ReentrantLock里其他代码的应该不会对这段代码陌生。但是这里的抛出打断异常,缺失lock()方法整个实现过程中都没有的
} //执行到这里是应为前两部都返回了真,也就是当前线程确实被打断,那么紧接着就抛出打断异常。到此即是原因
} finally {
if (failed)
cancelAcquire(node);
}
}
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this); //调用LockSupport阻塞当前线程
return Thread.interrupted(); //阻塞结束时判断是顺利被前继线程唤醒,还是被打断
}
插一句:wait()和sleep()都会响应打断操作