概述
介绍java并发包中的Lock锁的基本使用与实现细节。
1 Lock接口
lock锁可以实现和synchroinzed关键字类似的同步功能。
- Synchronized是关键字,内置语言实现,Lock是接口。
- Lock在使用的时候需要显示的去获取锁与释放锁,缺少比synchroinzed关键字的获取释放锁的便捷性。
- Synchronized在线程发生异常时会自动释放锁,因此不会发生异常死锁。Lock异常时不会自动释放锁,所以需要在finally中实现释放锁。
- Lock是可以中断锁,Synchronized是非中断锁,必须等待线程执行完成释放锁。
- Lock可以超时获取锁,Synchronized不具备。
Lock lock = new ReentrantLock();
lock.lock();
try {
} finally {
lock.unlock();
}
在finally块中释放锁,目的是保证在获得锁后,最终能够被释放。
注意:不要讲获取锁的过程写在try块中,因为如果在获取锁时发生了异常,异常抛出的同时,也会导致锁无故释放。
2 LockSupport
介绍ReentrantLock之前,先了解LockSupport的一些用法。
LockSupport定义了一组的公共静态方法,提供了最基本的线程阻塞和唤醒功能。

public class ParkDemo {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(() -> {
System.out.println("thread strat");
// LockSupport.parkNanos(TimeUnit.SECONDS.toNanos(5));,阻塞线程,5s后没有LockSupport.unpark自动返回
// 阻塞线程
LockSupport.park();
System.out.println("thread end");
});
thread.start();
TimeUnit.SECONDS.sleep(3);
// 唤醒线程thread
LockSupport.unpark(thread);
}
}
3 ReentrantLock
3.1 ReentrantLock类图

从ReentrantLock类图中我们可以发现AbstractQueuedSynchronizer即队列同步器。它是一个同步工具也是 Lock 用来实现线程同步的核心组件。AQS 队列内部维护的是一个FIFO 的双向链表,这种结构的特点是每个数据结构都有两个指针,分别指向直接的后继节点和直接前驱节点。所以双向链表可以从任意一个节点开始很方便的访问前驱和后继。每个 Node 其实是由线程封装,当线程争抢锁失败后会封装成 Node 加入到 ASQ 队列中去;当获取锁的线程释放锁以后,会从队列中唤醒一个阻塞的节点(线程)。
3.2 ReentrantLock#lock
public ReentrantLock() {
sync = new NonfairSync();
}
public void lock() {
sync.lock();
}
实际上默认的锁为非公平锁。sync 实际上是一个抽象的静态内部类,它继承了 AQS
Sync 有两个具体的实现类,分别是:
NofairSync:表示可以存在抢占锁的功能,也就是说不管当前队列上是否存在其他线程等待,新线程都有机会抢占锁。
FailSync: 表示所有线程严格按照 FIFO 来获取锁。
公平锁与非公平锁的区别在于:线程获取锁的顺序并不是按照申请锁的顺序,有可能后申请的线程比先申请的线程优先获取锁
以下以非公平锁为例子进行源码分析。
final void lock() {
//state 为0即表示当前没有锁,通过CAS尝试去获取锁,如果获取成功,设置当前线程获取到锁
if (compareAndSetState(0, 1))
// java.util.concurrent.locks.AbstractOwnableSynchronizer#exclusiveOwnerThread为当前线程
setExclusiveOwnerThread(Thread.currentThread());
// 如果cas失败,需要把当前线程加入同步队列进行等待
else
acquire(1);
}
state 是 AQS 中的一个属性,它在不同的实现中所表达的含义不一样,对于重入锁的实现来说,表示一个同步状态。它有两个含义的表示
-
当 state=0 时,表示无锁状态
-
当 state>0 时,表示已经有线程获得了锁,也就是 state=1,但是因为ReentrantLock 允许重入,所以同一个线程多次获得同步锁的时候,state 会递增,比如重入 5 次,那么 state=5。而在释放锁的时候,同样需要释放 5 次直到 state=0其他线程才有资格获得锁。
继续查看 acquire(1)方法,该方法存在于 AbstractQueuedSynchronizer 类,该类是 java.util.concurent.locks 锁的队列机制实现类
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
这个方法的主要逻辑是
-
通过 tryAcquire 尝试获取独占锁,如果成功返回 true,失败返回 false
-
如果 tryAcquire 失败,则会通过 addWaiter 方法将当前线程封装成 Node 添加到 AQS 队列尾部
-
acquireQueued,将Node作为参数,通过自旋去尝试获取锁。
3.2.1 tryAcquire
我们先分析tryAcquire的实现
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
我们一路跟踪进来,发现尝试获取锁的代码在 ReentrantLock内部类 Sync 汇总,Sync 是 NonFairSync 和 FairSync 的父类。
final boolean nonfairTryAcquire(int acquires) {
// 当前线程
final Thread current = Thread.currentThread();
// 锁的状态
int c = getState();
// 无锁状态
if (c == 0) {
// cas替换state的值,cas成功表示获取锁成功
if (compareAndSetState(0, acquires)) {
// 设置获得锁的线程为当前线程
setExclusiveOwnerThread(current);
return true;
}
}
// state不为0且获得锁的线程为当前线程
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;
}
如果 tryAcquire(arg) 返回 true,则不会执行 acquireQueued,表示成功获取锁,如果 tryAcquire(arg) 返回 false, 说明没有成功获取锁,则加入请求队列中。接着请看 addWaiter (Node.EXCLUSIVE) 方法。
3.2.2 addWaiter
private Node addWaiter(Node mode) {
// mode 为Node.EXCLUSIVE独占锁
// 把当前线程封装为 Node
Node node = new Node(Thread.currentThread(), mode);
// tail是AQS中表示同比队列队尾的属性,默认是 null
Node pred = tail;
// tail不为空的情况下,说明队列中存在节点(有线程已经在该锁上等待)
if (pred != null) {
node.prev = pred;
// 如果cas成功。把node加入到AQS队列,也就是设置为tail。
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果tail为空或者cas设置tail失败
enq(node);
return node;
}
addWaiter 将当前线程封装成Node后加入AQS队列
-
将当前线程封装成 Node
-
当前链表中的 tail 节点是否为空,如果不为空,则通过 cas 操作把当前线程的node 添加到 AQS 队列
-
如果tail为空或者 cas 失败,调用 enq 将节点添加到 AQS 队列
下图为加入AQS队列时,CAS失败的节点链表

3.2.3 enq
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// AQS队列为空,先加入一个空Node作为head与tail后通过自旋把node加入AQS
if (t == null) {
// CAS失败说明存在head与tail,通过自旋把node节点加入AQS队列
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 把node加入AQS队列
node.prev = t;
// CAS失败,通过自旋把node加入AQS队列
if (compareAndSetTail(t, node)) {
t.next = node;
// 返回原先的尾节点
return t;
}
}
}
}
使用自旋来加入,需要初始化一个head 节点,也就是 head 节点并不代表一个等待获取锁的对象,AbstractQueuedSynchronzier 选择初始化 head,tail 的时机为第一次产生锁争用的时候。初始化head,tail,设置成功后,再将新添加的节点放入到队列的尾部,然后该方法会返回原先的尾节点。
图解分析

3.2.4 acquireQueued
返回是否中断标志(interrupt)。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
// 此节点的前驱结点
final Node p = node.predecessor();
// 前驱结点为head,有资格去争抢锁且获取锁成功。(有线程释放锁,锁的释放见下面的分析)
if (p == head && tryAcquire(arg)) {
// 1、把当前结点设置为head
// 2、head的线程设置为null
// 3、head的前驱结点为null
setHead(node);
// 把原 head 节点从链表中移除
p.next = null; // help GC
failed = false;
return interrupted;
}
// 获取锁的线程还未释放
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 返回当前线程在等待过程中有没有中断过。
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
private void setHead(Node node) {
head = node;
node.thread = null;
node.prev = null;
}
通过 addWaiter 方法把线程添加到链表后,会接着把 Node 作为参数传递给
acquireQueued 方法,去竞争锁
-
获取当前节点的 prev 节点
-
如果 prev 节点为 head 节点,那么它就有资格去争抢锁,调用 tryAcquire 抢占锁
-
抢占锁成功以后,把获得锁的节点设置为 head,并且移除原来的初始化 head节点
-
如果获得锁失败,则根据 waitStatus 决定是否需要挂起线程及是否有中断
-
发生异常,通过 cancelAcquire 取消获得锁的操作。
3.2.5 shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 前置结点
int ws = pred.waitStatus;
// 如果前置节点为 SIGNAL,意味着只需要等待其他前置节点的线程被释放
if (ws == Node.SIGNAL)
//返回 true,意味着可以直接放心的挂起了
return true;
// ws大于0,意味着prev节点取消了排队,直接移除这个节点就行
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0); //这里采用循环,从双向列表中移除 CANCELLED 的节点
pred.next = node;
} else {
// 利用cas设置prev节点的状态为 SIGNAL(-1), 自旋时下次即可返回为true,把此节点的线程park
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
Node 有 5 中状态,分别是:CANCELLED(1),SIGNAL(-1)、CONDITION(- 2)、PROPAGATE(-3)、默认状态(0)
CANCELLED: 在同步队列中等待的线程等待超时或被中断,需要从同步队列中取消该 Node 的结点, 其结点的 waitStatus 为 CANCELLED,即结束状态,进入该状态后的结点将不会再变化。
SIGNAL: 只要前置节点释放锁,就会通知标识为 SIGNAL 状态的后续节点的线程。
CONDITION: 和 Condition 有关系,后续会讲解
PROPAGATE:共享模式下,PROPAGATE 状态的线程处于可运行状态
0:初始状态
CANCELLED节点移除示例图

3.2.6 parkAndCheckInterrupt
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
使用 LockSupport.park 挂起当前线程变成 WATING 状态。
Thread.interrupted,返回当前线程是否被其他线程触发过中断请求,也就是thread.interrupt(); 如果有触发过中断请求,那么这个方法会返回当前的中断标识true,并且对中断标识进行复位标识已经响应过了中断请求。如果返回 true,意味着在 acquire 方法中会执行 selfInterrupt()。
selfInterrupt: 标识如果当前线程在 acquireQueued 中被中断过,则需要产生一个中断请求,原因是线程在调用 acquireQueued 方法的时候是不会响应中断请求。如果线程在等待过程中被中断过,它是不响应的。只是获取资源后才再进行自我中断selfInterrupt(),将中断补上
static void selfInterrupt() {
Thread.currentThread().interrupt();
}

为了直观体现上述获取锁的过程,现给出如下流程图:

3.3 ReentrantLock#unlock
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
// 释放锁成功
if (tryRelease(arg)) {
// 得到 aqs 中 head 节点
Node h = head;
//如果 head 节点不为空并且状态=0.调用 unparkSuccessor(h)唤醒后继节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
3.3.1 tryRelease
先查看tryRelease代码
protected final boolean tryRelease(int releases) {
// 计算持有锁的次数=当前被持有锁的次数-减去释放的锁的数量
int c = getState() - releases;
// 判断当前锁的持有线程释放与释放锁的线程是否相同,否则,直接抛出运行时异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 如果释放锁后,占有次数为0,则代表该锁被释放,设置锁的占有线程为null
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
// 设置锁的state,如果返回true,表示锁被释放,如果返回false,表示,锁继续被该线程占有(重入了多次,就需要释放多次)。
setState(c);
return free;
}
这个方法可以认为是一个设置锁状态的操作,通过将 state 状态减掉传入的参数值(参数是 1),如果结果状态为 0,就将排它锁的 Owner 设置为 null,以使得其它的线程有机会进行执行。在排它锁中,加锁的时候状态会增加 1,在解锁的时候减掉 1,同一个锁,在可以重入后,可能会被叠加为 2、3、4 这些值,只有 unlock()的次数与 lock()的次数对应才会将 Owner 线程设置为空,而且也只有这种情况下才会返回 true。
3.3.2 release
分析完tryRelease方法,返回为true,再分析release方法。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
代码@为什么需要判断 h!=null && h.waitStatus != 0的判断呢?
在讲解获取锁的时候,方法 shouldParkAfterFailedAcquire 中对于代码的讲解,其实不难发现,一个节点在请求锁时,只有当它的前驱节点的waitStatus=Node.SIGNAL时,才会阻塞。如果 head为空,则说明AQS队列为空,压根就不会有线程阻塞,故无需执行 unparkSuccessor(h), 同样的道理,如果根节点的waitStatus=0,则说明压根就没有 head 后继节点,故也没有线程被阻塞这一说。head如果不为空,该节点代表获取锁的那个线程。
3.3.3 unparkSuccessor
private void unparkSuccessor(Node node) {
// node为head节点,获得 head 节点的状态
int ws = node.waitStatus;
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// head的后继节点
Node s = node.next;
// 如果下一个节点为 null 或者 status>0 表示 cancelled 状态
if (s == null || s.waitStatus > 0) {
s = null;
// 从尾部节点开始扫描,找到距离 head 最近的一个waitStatus<=0 的节点
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
// next 节点不为空,直接唤醒这个线程即可
if (s != null)
LockSupport.unpark(s.thread);
}
为什么在释放锁的时候是从 tail 进行扫描?
我们再回到 enq那个方法或者addWaiter方法
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// AQS队列为空,先加入一个空Node作为head与tail后通过自旋把node加入AQS
if (t == null) {
// CAS失败说明存在head与tail,通过自旋把node节点加入AQS队列
if (compareAndSetHead(new Node()))
tail = head;
} else {
// 把node加入AQS队列
node.prev = t;
// CAS失败,通过自旋把node加入AQS队列
if (compareAndSetTail(t, node)) {
t.next = node;
// 返回原先的尾节点
return t;
}
}
}
}
private Node addWaiter(Node mode) {
// mode 为Node.EXCLUSIVE独占锁
// 把当前线程封装为 Node
Node node = new Node(Thread.currentThread(), mode);
// tail是AQS中表示同比队列队尾的属性,默认是 null
Node pred = tail;
// tail不为空的情况下,说明队列中存在节点(有线程已经在该锁上等待)
if (pred != null) {
node.prev = pred;
// 如果cas成功。把node加入到AQS队列,也就是设置为tail。
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 如果tail为空或者cas设置tail失败
enq(node);
return node;
}
-
将新的节点的 prev 指向 tail
-
通过 cas 将 tail 设置为新的节点,因为 cas 是原子操作所以能够保证线程安全性
-
t.next=node;设置原 tail 的 next 节点指向新的节点

在 cas 操作之后,t.next=node 操作之前。存在其他线程调用 unlock 方法从 head开始往后遍历,由于 t.next=node 还没执行意味着链表的关系还没有建立完整。
就会导致遍历到 t 节点的时候被中断。所以从后往前遍历,一定不会存在这个问题。
图解分析

通过锁的释放,原本的结构就发生了一些变化。head 节点的 waitStatus 变成了 0,ThreadB 被唤醒。
当LockSupport.unpark(s.thread)时,原本挂起的线程被唤醒以后继续执行,应该从哪里执行呢?从acquireQueued的代码parkAndCheckInterrupt方法会解除阻塞,继续放下执行,进入到 acquireQueued的for循环处详细解释见acquireQueued。
图解分析

释放锁的流程图

3.4 公平锁和非公平锁的区别
锁的公平性是相对于获取锁的顺序而言的,如果是一个公平锁,那么锁的获取顺序就应该符合请求的绝对时间顺序,也就是 FIFO。 在上面分析的例子来说,只要CAS 设置同步状态成功,则表示当前线程获取了锁,而公平锁则不一样。非公平锁在获取锁的时候,会先通过 CAS 进行抢占,而公平锁则不会。
FairSync.tryAcquire
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 同步队列中当前节点是否有前驱节点,该方法返回 true,则表示有线程比当前线程更早地请求获取锁,因此需要等待前驱线程获取并释放锁之后才能继续获取锁
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;
}
public final boolean hasQueuedPredecessors() {
// 尾节点
Node t = tail;
// 头节点
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
- 如果h==t成立,h和t均为null或是同一个具体的节点,无后继节点,返回false
- 如果h!=t成立,head.next是否为null,如果为null,返回true。什么情况下h!=t的同时h.next==null??,有其他线程第一次正在入队时,可能会出现。见AQS的enq方法,compareAndSetHead(node)完成,还没执行tail=head语句时,此时tail=null,head=newNode,head.next=null。
- 如果h!=t成立,head.next != null,则判断head.next是否是当前线程,如果是返回false,否则返回true(head节点是获取到锁的节点,但是任意时刻head节点可能占用着锁,也可能释放了锁(unlock()),未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁)
3.5 ReentrantLock#tryLock
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
tryLock它表示用来尝试获取锁,如果获取成功,则返回true,如果获取失败(即锁已被其他线程获取),则返回false,这个方法无论如何都会立即返回。在拿不到锁时不会一直在那等待。
3.6 ReentrantLock#tryLock(long timeout, TimeUnit unit)
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 调用tryAcquire先尝试获取锁,如果失败则调用doAcquireNanos
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
3.6.1 doAcquireNanos
tryAcquire前面已经分析过,以下分析doAcquireNanos的源码
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (nanosTimeout <= 0L)
return false;
// 计算出到期时间
final long deadline = System.nanoTime() + nanosTimeout;
// 将当前线程封装成Node后加入AQS队列(前面已经分析过)
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
// 反复重试直到成功获取锁或者超时返回false
for (;;) {
final Node p = node.predecessor();
// 该节点的前驱节点是头节点则尝试获取锁
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
// 剩余时间
nanosTimeout = deadline - System.nanoTime();
// 超时返回false
if (nanosTimeout <= 0L)
return false;
// shouldParkAfterFailedAcquire 判断是否需要阻塞线程 并且剩余时间大于 spinForTimeoutThreshold (默认1000)
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
// 阻塞线程,超时唤醒去尝试抢占锁
// 并发问题时,当前驱结点释放锁时。锁被新加入的线程抢占,此时需要重新park,时间为剩余的时间
LockSupport.parkNanos(this, nanosTimeout);
// 中断检测
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
// 发生异常, park超时唤醒并且没有成功抢占锁
if (failed)
// 把当前结点从AQS队列中取消
cancelAcquire(node);
}
}
3.6.2 cancelAcquire
超时没有获取到锁,将添加的节点取消。
private void cancelAcquire(Node node) {
if (node == null)
return;
// 设置node的线程为null
node.thread = null;
// 当前节点的前驱节点
Node pred = node.prev;
// 设置prev的值为从当前取消节点往head节点方向,第一个未取消节点。并将中间的取消节点脱离这条链。
// 跳过被cancel的前继node,找到一个有效的前继节点pred
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
// 设置当前节点为CANCELLED状态
node.waitStatus = Node.CANCELLED;
// 如果被取消的节点是尾节点的话,那么将pred设置为尾节点。如果设置失败,说明,有别的线程在申请锁,使得尾部节点发生了变化
if (node == tail && compareAndSetTail(node, pred)) {
// 如果设置成功了,既然pred是尾节点,那么再次将pred的next域设置为null;当然也能设置失败,表明又有新的线程在申请锁,创建了新的next节点
compareAndSetNext(pred, predNext, null);
} else {
// 如果取消的节点,不是尾部节点或者CAS设置尾节点失败
int ws;
// pred不是head节点
// pred的waitStatus为SIGNAL。或者设置<=0且设置为SIGNAL成功
// pred.thread 的线程不为空
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
// 待取消的节点的 next
Node next = node.next;
// 待取消的节点的 next不为空,并且状态为非取消的时,将 pred.next 设置为 node.next;该取消节点被删除
if (next != null && next.waitStatus <= 0)
compareAndSetNext(pred, predNext, next);
} else {
// 如果pred为head执行唤醒操作即node是head的后继节点
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
-
node是tail
image-20200907235956180
-
node既不是tail,也不是head的后继节点
image-20200908000917861
将successor指向pred是谁干的?
是别的线程做的。当别的线程在调用shouldParkAfterFailedAcquire()时,会根据prev指针跳过被cancel掉的前继节点,同时,会调整其遍历过的prev指针 -
处于Node.CANCEL状态节点的删除发生在shouldParkAfterFailedAcquire,一处就发生在cancelAcquire方法。
3.7 ReentrantLock#lockInterruptibly
lock(),通过该方法去获取锁,如果锁被占用,线程阻塞,如果调用被阻塞线程的interupt()方法,会取消获取锁吗?答案是否定的。
首先需要知道 LockSupport.park 会响应中断,但不会抛出 InterruptedException。接下来,我们就从lockInterruptibly()方法入手,一步一步解析,并分析与lock方法的差异。
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
// 如果尝试获取锁失败后,进入获取锁并等待锁逻辑。
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
// 与lock逻辑类似
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())
// 如果 parkAndCheckInterrupt 如果是通过 t.interupt 方法,使LockSupport.park 取消阻塞的话,会抛出 InterruptedException,
// 中断interupt相当于unpark,也会唤醒处于park状态的线程
// 停止尝试获取锁,然后将添加的节点取消
throw new InterruptedException();
}
} finally {
if (failed)
// 前面已经分析过
cancelAcquire(node);
}
}
网友评论