1、ReentrantLock介绍
首先我们需要清楚ReentrantLock并不是一种替代内置加锁的方法,而是当内置加锁机制不适用时,作为一种可选择的高级功能。ReentrantLock相比较Synchronized主要多了三个功能
- ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
- ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
- ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
在jdk1.5版本中ReentrantLock性能是远好于synchronized的,在1.6版本synchronized引入了轻量锁和偏向锁后,synchronized的性能和ReentrantLock大致差不多了,并且ReentrantLock是在jdk中实现的,如果我们在申请了一个ReentrantLock后在finally中没有去释放这个锁,这就相当于我们在代码中放置了一个炸弹,随时都有可能引发系统崩溃并且我们也不容易查到哪里是源头。但synchronized是关键字,会自动关闭,调用也更加方便,所以在一般情况下优先还是调用synchronized,只有在用到ReenTrantLock特有的功能时我们去调用ReenTrantLock,并且一定要注意在try语句中申请一个Lock对象后,一定要在finally语句中取关闭这个Lock对象。
下面将根据ReentrantLock的三个特有的功能来一一进行分析
2、ReentrantLock加锁解锁
2.1、介绍
在公平锁中,线程将按照他们发出请求的顺序来获取锁;但在非公平锁中,则允许"插队",如果一个线程在请求给公平锁的时候,如果在发出请求的同时该锁的状态变为可用,那么这个锁将跳过队列所有的等待线程并获取这个锁。在激烈竞争(线程持有锁的时间相对较短)这种情况下,非公平锁的性能要高于公平锁的性能,原因在于恢复一个被挂起的线程于该线程真正开始运行之间存在着严重的延迟。
可以看出在非公平锁中将唤醒线程B的那段时间也充分利用了起来,在公平锁中唤醒一个挂起的线程期间是不能有其他线程来占用这个锁,这段时间锁是不被任何线程占用的,是属于空闲的。在非公平锁中,在一个线程别唤醒之前是可以允许其他线程去占用这锁的,如果其他线程占用这个锁的时间非常的短,在需要唤醒的那个线程唤醒之前就释放掉了,那么这个唤醒的线程又可以占用这个锁,当然如果被唤醒的那个线程唤醒后其他线程依然占用着这个锁,那么这个被唤醒的线程只能再次被挂起放入等待队列(是有点郁闷,但是之后总能等到机会的)。所以非公平锁在线程占用锁的时间相对较短情况下其性能是比公平锁的性能要高上不少。
2.2、源码分析
ReentrantLock如何加锁
首先来看下ReentrantLock类的依赖关系,ReentrantLock内部定义了一个内部类为Sync,同时ReentrantLock内部也定义了两个继承自Sync的实现类,分别为FairSync和NonfairSync类。我们调用ReentrantLock的lock功能实际上是用内部Sync类的lock函数来实现的,同时Sync类的lock函数的实现也是基于FairSync或者NonfairSync类的lock,所以本质上调用ReentrantLock的lock函数,实际上调用到的是FairSync的lock函数或者NonfairSync的lock函数。
接下来让我们来看下具体的源码
/*ReentrantLock的lock函数*/
public void lock() {
sync.lock();
}
/*我们在新建一个ReentrantLock对象时会根据是否公平来初始化sync*/
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
这样调用sync的lock,就是调用的FairSync的lock或者NonfairSync的lock,我们先来看下FairSync类
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
/*调用lock函数*/
final void lock() {
acquire(1);
}
}
其中acquire函数实际实现是在AbstractQueuedSynchronizer类中,这个类是Sync的父类,而Sync是FairSync的父类,所以在FairSync中调用acquire函数能够调用到AbstractQueuedSynchronizer类的acquire实现
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
tryAcquire函数分析
在acquire函数中首先会调用到tryAcquire函数,在AbstractQueuedSynchronizer类中没有这个实现,这边实现是在FairSync中
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
/*表示当前还没有线程占用这个锁
* 1、hasQueuedPredecessors函数判断当前阻塞队列上没有先来的线程在等待,UnfairSync这里的实现就不一致
* 2、hasQueuedPredecessors为false才能进入到compareAndSetState函数,如果hasQueuedPredecessors为fales,
* 当前等待队列上没有线程正在等待,如果为true,表示有线程等待,在公平锁中不会再这个线程中取获取锁,而是等待队列上第一个线程去获取
* 3、compareAndSetState函数来设置表示独占线程的变量,其中用到了CAS,acquires为1,表示如果有一个线程占用之后,state这个变量就变成了1
* 4、如果设置表示独占线程的变量为1成功后,就调用setExclusiveOwnerThread设置当前占用锁的线程,
* 在AbstractOwnableSynchronizer类中exclusiveOwnerThread表示占用的线程
*/
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
/*说明已经有线程占用当前的锁,首先判断当前的线程是否是占用锁的线程,如果是,则重入state的值加上一*/
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
这里getState获取AbstractQueuedSynchronizer类中表示线程独占的变量state的值,如果state为0,表示还没有线程占有这个变量,也就是还没有占用锁,如果state为1,说明了已经有线程占用了这个锁。然后在公平锁中会先去判断在等待队列中是否有线程正在等待这个锁,如果有线程正在等待,那么这个线程就不会去获取锁,使等待队列中第一个线程去获取锁。获取到锁的线程利用setExclusiveOwnerThread来设置当前占用线程的信息,这个信息存放在AbstractOwnableSynchronizer类中。如果在一开始判断为已经有线程占用了这个锁,还需要去判断一下当前的线程是否是占用锁的线程,因为同一线程是可以重入的,只需要将state这个表示独占线程的变量加上一。
acquireQueued函数分析
在acquire函数中如果tryAcquire返回true,表示当前线程已经获取到了独占锁。则acquire也会直接结束,如果tryAcquire返回false,继续进入到acquireQueued(addWaiter(Node.EXCLUSIVE), arg))函数
private Node addWaiter(Node mode) {
/*创建一个节点,类型mode为EXCLUSIVE类型*/
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
/*更新tail对象,因为末尾新增加了一个节点,所以tail的对象应该指向最新的节点*/
Node pred = tail;
/*
*先使用快速入列法来尝试一下,如果失败,则进行更加完备的入列算法.
*只有在必要的情况下才会使用更加复杂耗时的算法,也就是乐观的态度
*/
if (pred != null) {
node.prev = pred;
/*以CAS的方式进行更新tail对象的值*/
if (compareAndSetTail(pred, node)) {
/*更新tail节点的next值*/
pred.next = node;
return node;
}
}
/*CAS失败,或在pred == null时调用enq*/
enq(node);
return node;
}
在addWaiter函数首先会快速尝试添加一次,如果CAS操作失败了,则会调用到enq函数,这里首次调用或者上面CAS操作失败了会调用enq函数,在enq函数中会不断循环调用CAS操作,这就是经典的不断尝试CAS操作从而最终能够完成操作。
private Node enq(final Node node) {
/*cas无锁算法的标准for循环,不停的尝试,直到成功后返回*/
for (;;) {
Node t = tail;
/*初始化,需要注意的是head是一个哨兵的作用,并不代表某个要获取锁的线程节点*/
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
/*不断尝试添加,直到成功,并且要注意node这个参数是根据当前线程信息得到的,不同的线程之间的node参数不同
所以也不用担心其他线程会先把这里的node添加进去,就是可能会有先后,但最终总能添加上,不停的尝试,自旋锁*/
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
在调用addWaiter函数后就将当前的节点已经添加到AbstractQueuedSynchronizer中tail参数的next上,类中还有head参数,这个只是一个哨兵的作用,并不代表某个要获取锁的线程节点。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
/*获取当前节点的前置节点*/
final Node p = node.predecessor();
/*首先判断这个前置节点是否为head节点,如果不是head节点就跳过判断,如果是的话说明下一个获取锁的节点就是node节点
因为head只是一个哨兵,不代表实际所要申请锁的节点,这里首先尝试去获取一下锁*/
if (p == head && tryAcquire(arg)) {
/*如果获取成功,则将head节点更新尾node节点*/
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
/*判断当前node节点如果需要进行堵塞*/
if (shouldParkAfterFailedAcquire(p, node) &&
/*如果需要堵塞,则调用下面这个函数来真正执行堵塞操作*/
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
由于进入阻塞状态的操作会降低执行效率,所以,AQS会尽力避免试图获取独占性变量的线程进入阻塞状态。所以,当线程加入等待队列之后,acquireQueued会执行一个for循环,每次都判断当前节点是否应该获得这个变量(在队首了)。如果不应该获取或在再次尝试获取失败,那么就调用shouldParkAfterFailedAcquire判断是否应该进入阻塞状态。如果当前节点之前的节点已经进入阻塞状态了,那么就可以判定当前节点不可能获取到锁,为了防止CPU不停的执行for循环,消耗CPU资源,调用parkAndCheckInterrupt函数来进入阻塞状态。
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
/*获取前置节点的等待状态*/
int ws = pred.waitStatus;
/*前一个节点在等待独占性变量释放的通知,所以,当前节点可以阻塞*/
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
/*表示前一个节点处于取消获取独占性变量的状态,就是表示节点不想要去获取锁了,所以可以跳过去*/
if (ws > 0) {
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0); //遍历查找
pred.next = node;
} else {
/*在这种状态下设置前置节点状态为SIGNAL,这样在下一个循环中可以直接在上面返回true从而挂起该线程*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
具体挂起线程的方法是parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted(); //判断当前线程是否已经被中断,如果被中断,返回true,如果没有被中断返回false
}
//LockSupport类的park函数
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker); //设置阻塞对象,用来记录线程被谁阻塞的,用于线程监控和分析工具来定位
UNSAFE.park(false, 0L); //让当前线程不再被线程调度,就是当前线程不再执行.
setBlocker(t, null);
}
ReentrantLock申请加锁总结
AbstractQueuedSynchronizer这个类中有锁的状态变量state,如果state为0表示还没有线程获取到锁,如果为1则表示有线程获取到锁。在AbstractQueuedSynchronizer类中还定义了一个内部类Node,这个Node可以当前线程变量和mode组成,可以串联多个线程,并且这个Node类中还有tail,head,prev这些参数,这样我们就可以由这个类变量来组成一系类线程的队列,这样我们就可以在AbstractQueuedSynchronizer类中构建一个线程等待队列,可以做到依次获取锁。
而如何表示当前获取锁的线程的哪一个线程呢?这些变量保存在AbstractOwnableSynchronizer类中。我们通过CAS方式去获取线程独占变量state,并设置他而实现获取锁的操作。
ReentrantLock如何释放锁
在上面已经知道了ReentrantLock的加锁流程,是通过对独占线程变量state进行操作来判断是否可以获取锁,如果一个线程第一次获取锁,就讲state变量赋值为1,然后如果这个线程重复去获得这个锁也是可以的,这时候这个线程重复n次就讲state的值赋值为n。那我们也可以想到解锁就是将这个state降下下来,并且如果state为0,就可以通知下一个线程可以去获取锁了。接下来我们一起对照对源码看下
public void unlock() {
sync.release(1);
}
同样ReentrantLock的unlock函数是在AbstractQueuedSynchronizer类中实现的,并没有在FairSync和NonfairSync类中分别进行实现。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
/*waitStatus为0表示这个节点是刚创建的或者刚被释放掉线程的node,不用进行释放操作
如果node刚创建则waitStatus为0,在acquireQueued中添加到等待队列并设置waitStatus为-1*/
if (h != null && h.waitStatus != 0)
unparkSuccessor(h); //进行唤醒线程操作
return true;
}
return false;
}
tryRelease函数是在ReentrantLock类中实现的
protected final boolean tryRelease(int releases) {
/*c表示当前的状态值-1后的值*/
int c = getState() - releases;
/*判断当前的线程是否是占有锁的那个线程,如果不是占有锁,就不能进行释放锁操作*/
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
/*如果c为0了,表示这个线程不应该再占用这个锁了*/
if (c == 0) {
free = true;
setExclusiveOwnerThread(null); //设置现在没有线程占用锁
}
setState(c); //设置表示独占线程变量的值
return free;
}
这边可能有疑问为什么设置的是否不需要进行CAS操作,例如在setState函数时可能有其他线程也进行设置。注意这里是不需要进行CAS操作的,因为占用锁的线程只会有一个,而其他没有占用锁的线程在上面判断是否是占用锁的线程后就已经扔出异常了。所以一般正常情况只有占用锁的线程才会调用这个函数,因此不需要担心其他线程会进行修改。如果成功释放锁,则返回true,如果这个线程是重入的,也就是这一次状态值-1后这个线程还是占用着这个锁,则返回false。
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
/*如果ws<0,则表示这个节点代表的线程是准备去申请锁的,所以这里设置为0表示这个线程不再会去申请锁了,因为这个node节点已经获取到锁了,
现在的目的就是唤醒下一个线程去获取锁
*/
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
/*找到合适的节点,因为s.waitStatus > 0表示这个节点已经取消获取锁了,自然也不用释放*/
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
/*进行释放操作*/
if (s != null)
LockSupport.unpark(s.thread);
}
这边唤醒锁的操作也相对容易理解,就是设置了下一个线程对应node的waitStatus值,并使用LockSupport去唤醒下一个线程。
2.2、总结
3、ReentrantLock特性分析
3.1、ReentrantLock的公平锁和非公平锁
上面其实已经介绍了公平锁和非公平锁,下来结合源码来分析一下他们之间的差别
/*公平锁获取锁的函数*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
/*非公平锁获取锁的函数*/
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;
}
这边的差别就只是!hasQueuedPredecessors()这个判断语句而已,在公平锁中如果发现当前的独占线程变量值为0,则一个函数可以去获取锁,在公平锁中先会判断在等待队列中是否有等待线程,如果有等待线程,那么先会让等待线程去尝试占取这把锁,当前的线程加入到等待队列的末尾。在非公平锁中,如果发现独占线程变量的值为0,则当前线程先会去尝试占取这把锁。如果当前线程没有成功占取到锁,也还是会将这个线程放入到等待队列中,所以不是只有公平锁中才会有等待队列,非公平锁中只是会让当前线程先去占取这把锁,但是也有可能当前线程占取不到这把锁,这时候就需要对当前线程进行处理,否则一直尝试占取会平白消耗系统性能。
3.2、ReentrantLock的Condition特性
Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set (wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
在Condition中,用await()替换wait(),用signal()替换notify(),用signalAll()替换notifyAll(),传统线程的通信方式,Condition都可以实现,这里注意,Condition是被绑定到Lock上的,要创建一个Lock的Condition必须用newCondition()方法。Condition的强大之处在于它可以为多个线程间建立不同的Condition。
class BoundedBuffer {
final Lock lock = new ReentrantLock();//锁对象
final Condition notFull = lock.newCondition();//写线程条件
final Condition notEmpty = lock.newCondition();//读线程条件
final Object[] items = new Object[100];//缓存队列
int putptr/*写索引*/, takeptr/*读索引*/, count/*队列中存在的数据个数*/;
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)//如果队列满了
notFull.await();//阻塞写线程
items[putptr] = x;//赋值
if (++putptr == items.length) putptr = 0;//如果写索引写到队列的最后一个位置了,那么置为0
++count;//个数++
notEmpty.signal();//唤醒读线程
} finally {
lock.unlock();
}
}
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)//如果队列为空
notEmpty.await();//阻塞读线程
Object x = items[takeptr];//取值
if (++takeptr == items.length) takeptr = 0;//如果读索引读到队列的最后一个位置了,那么置为0
--count;//个数--
notFull.signal();//唤醒写线程
return x;
} finally {
lock.unlock();
}
}
}
在上面就建了两个Condition变量,在队列为空时,notEmpty被唤醒,堵塞了读进程,当队列为满时,notFull被唤醒,堵塞了写进程。所以Condition能够更加精细的控制多线程的休眠与唤醒。对于同一个锁,我们可以创建多个Condition,在不同的情况下使用不同的Condition。如果采用Object类中的wait(),notify(),notifyAll()实现该缓冲区,当向缓冲区写入数据之后需要唤醒"读线程"时,不可能通过notify()或notifyAll()明确的指定唤醒"读线程",而只能通过notifyAll唤醒所有线程(但是notifyAll无法区分唤醒的线程是读线程,还是写线程)。 但是,通过Condition,就能明确的指定唤醒读线程。
源码分析
根据上面例子我们可以知道在ReentrantLock中创建Condition对象是在lock函数中调用newCondition函数的
final ConditionObject newCondition() {
return new ConditionObject();
}
所以实际上是创建了一个ConditionObject对象,ConditionObject类是在AbstractQueuedSynchronizer定义的,是AbstractQueuedSynchronizer类的内部类。先来看下这个类中await函数
public final void await() throws InterruptedException {
/*判断当前线程是否是被中断的*/
if (Thread.interrupted())
throw new InterruptedException();
/*在condition wait queue队列中添加一个节点*/
Node node = addConditionWaiter();
/*将当前线程持有的锁释放掉,这样这个线程就不占用这个锁了*/
int savedState = fullyRelease(node);
int interruptMode = 0;
/*判断当前节点是否在sync队列,就是等待获取锁的队列,signal会将节点从condition wait queue队列移动到sync队列*/
while (!isOnSyncQueue(node)) {
/*还在condition wait queue队列,说明线程还在等待被唤醒,这时候调用park函数进行堵塞*/
LockSupport.park(this);
//判断是否被中断,线程从park函数返回有两种情况,一种是
//其他线程调用了unpark,另外一种是线程被中断,也就是说这两种情况都能时这个线程继续走下去
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
/*这边acquireQueued函数就是尝试去获取锁,其中如果获取到了,返回false,如果没有获取到则在等待队列中一直尝试去获取锁*/
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
/*这边判断是因为中断而跳出循环还是其他线程调用了unpark函数,如果是因为中断,继续将线程进行中断*/
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
下面在看下sign函数
public final void signal() {
/*判断是否是当前获得锁的线程,如果不是当前获取锁的线程,直接抛出错误,因为只有获得锁的线程才能进行操作*/
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
这边获得condition wait queue队列的头结点,如果不为空的话调用doSignal函数
private void doSignal(Node first) {
do {
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
/*
* 如果设置失败,说明该node已经被取消了,所以返回false,让doSignal继续向下通知其他未被取消的node
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*入sync队列*/
Node p = enq(node);
int ws = p.waitStatus;
/*
* 如果compareAndSetWaitStatus失败,所以直接unpark,让线程继续执行await中的
* 进行isOnSyncQueue判断的while循环,然后进入acquireQueue函数.
* 这里失败的原因可能是Lock其他线程释放掉了锁,同步设置p的waitStatus
* 如果compareAndSetWaitStatus成功了呢?那么该node就一直在acquire lock queue中
* 等待锁被释放掉再次抢夺锁,然后再unpark
*/
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
总的来说流程就是
因为ConditionObject是对象,可以创建多个,从而可以更加精细的控制多线程的休眠与唤醒。
3.3、ReentrantLock的可中断性
这个性质是调用Lock类的lockInterruptibly函数实现的,lockInterruptibly函数是前文lock函数的可中断版本,我们来看下ReentrantLock是如何实现这个功能的
public final void acquireInterruptibly(int arg)
throws InterruptedException {
/*判断当前线程是否是中断的*/
if (Thread.interrupted())
throw new InterruptedException();
/*尝试获取锁,如果获取不到调用到doAcquireInterruptibly函数中*/
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
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())
//这是不同于doAcquire函数的地方,如果在阻塞过程中被中断直接抛出中断异常到上层函数,并且取消当前等待节点
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
可以看到通过直接扔出InterruptedException使得ReentrantLock锁是可以被中断的。