在日常开发的过程中,java.util.concurrent(J.U.C)是我们在并发编程中用的最多的,如果我们想熟练的掌握JAVA并发基础,少不了需要了解 AbstractQueuedSynchronizer(以下简写AQS)这个抽象类,因为它是 Java 并发包的基础工具类,是实现 ReentrantLock、CountDownLatch、Semaphore、FutureTask 等类的基础。
当然这篇文章是我自己对AQS的理解,有错误之处,请评论区留言交流。
并发基础之——AbstractQueuedSynchronizer的类结构
//比较重要的属性都在这里
// 头结点,我是把它当成当前持有锁的线程
private transient volatile Node head;
// 阻塞队列的尾节点,每个新的等待线程节点进来都插入到最后
private transient volatile Node tail;
// 代表当前锁的状态,0代表没有被占用,大于 0 代表有线程持有当前锁
// 并且这个值是可以大于 1,是因为锁是可以重入的,每次重入都加上 1
private volatile int state;
// 代表当前持有独占锁的线程
private transient Thread exclusiveOwnerThread;
用图片来阐述这个结构的话如下(有点丑,管用就行):
线程的每一个等待节点都被包装成了一个Node,Node类结构如下
static final class Node {
static final Node SHARED = new Node();
//标识节点当前在独占模式下
static final Node EXCLUSIVE = null;
//代码此线程取消了争抢这个锁
static final int CANCELLED = 1;
//表示当前这个node的后继节点对应的线程需要被唤醒
static final int SIGNAL = -1;
//表示这个节点在条件队列,后面说
static final int CONDITION = -2;
//后面分析
static final int PROPAGATE = -3;
//
// 这么理解,暂时只需要知道如果这个值 大于0 代表此线程取消了等待
// 如同我们日常买东西排队一样可以排到一半,我不想买了不排队了
volatile int waitStatus;
// 前驱节点的引用
volatile Node prev;
// 后继节点的引用
volatile Node next;
// 线程本身
volatile Thread thread;
}
前面讲的都是概念,但是如果不清楚这些概念,后面可能会看不下去所以还是不要略过,下面就是真正开始分析源码了
以ReentrantLock为例子,它是我们平常用得比较多的轻量级锁,它可以分为公平锁和非公平锁,并且它是可重入的,ReentrantLock 在内部用了内部类 Sync 来管理锁,所以真正的获取锁和释放锁是由 Sync 的实现类来控制的。因为ReentrantLock可以分为公平锁和非公平锁,可以再构造ReentrantLock的时候指定
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
下面我们从非公平锁的实现 FairSync切入源码
ReentrantLock加锁 (公平锁 FairLock)
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
//加锁
final void lock() {
acquire(1);
}
public final void acquire(int arg) {
// 首先调用tryAcquire(1)一下,顾名思义,先尝试加锁
// 因为此时有可能没有线程竞争有可能直接就成功了呢,也就不需要进等待队列排队了
// tryAcquire(1)没有成功,这个时候需要把当前线程挂起,放到阻塞队列中。
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg)) {
selfInterrupt();
}
}
// 尝试直接获取锁,返回值是boolean,代表是否获取到锁
// 返回true:1.没有线程在等待锁;2.重入锁
// 返回false 没有抢到锁
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// c == 0 表名此时此刻没有线程持有锁
if (c == 0) {
// 虽然此时此刻锁是可以用的,但是这是公平锁,既然是公平,就得讲究先来后到,
// 调用hasQueuedPredecessors()看看有没有别人在队列中等待
if (!hasQueuedPredecessors() &&
// 如果没有线程在等待,那就用CAS尝试一下,成功了就获取到锁了,
// 不成功的话,只能说明一个问题,就在刚刚几乎同一时刻有个线程抢先了
compareAndSetState(0, acquires)) {
// 到这里就是获取到锁了,标记一下现在是当前这个想成占用了锁
setExclusiveOwnerThread(current);
//返回
return true;
}
}
// 会进入这个分支,说明当前是有线程占用锁的,
// 但是会不会是同一个线程重入了,如果是重入的线程直接state+1
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
}
// 假设tryAcquire(arg) 返回false,那么代码将执行:
// acquireQueued(addWaiter(Node.EXCLUSIVE), arg),
// 此方法的作用是把线程包装成node,同时进入到队列中
// 参数mode此时是Node.EXCLUSIVE,代表独占模式
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// 以下几行代码想把当前node加到链表的最后面去,也就是进到阻塞队列的最后
Node pred = tail;
if (pred != null) {
// 将当前的队尾节点,设置为自己的前驱
node.prev = pred;
// 用CAS把自己设置为队尾, 如果成功了,tail == node
// 这个节点成为阻塞队列新的尾巴
if (compareAndSetTail(pred, node)) {
// 进到这里说明设置成功,当前node==tail, 将自己与之前的队尾相连,
// 上面已经有 node.prev = pred,加上下面这句,也就实现了和之前的尾节点双向连接了
pred.next = node;
// 线程入队了,可以返回了
return node;
}
}
// 仔细看看上面的代码,如果会到这里,
// 说明 (pred = tail)==null(队列是空的) 或者 CAS失败(有线程在竞争入队)
enq(node);
return node;
}
// 能走到这个方法只有两种可能:等待队列为空,或者有线程竞争入队,
// 这里会自旋配合CAS设置将自己设置为tail,竞争一次竞争不到,我就多次竞争,总会排到的
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 之前说过,队列为空也会进来这里
if (t == null) {
// 一开始 head 和 tail都是 null 的
// 还是一步CAS,你懂的,现在可能是很多线程同时进来呢
if (compareAndSetHead(new Node()))
// 给后面用:这个时候head节点的waitStatus==0
// 这个时候有了head,但是tail还是null,设置一下,
// 把tail指向head,放心,马上就有线程要来了,到时候tail就要被抢了
// 设置完了以后,继续for循环,下次就到下面的else分支了
tail = head;
} else {
// CAS设置自己为队尾,没有设置成功的话重复尝试直到成功
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
// 走完addWaiter之后,回到这段代码了,
// 这个时候当前节点肯定是已经成功进入了等待队列的
// if (!tryAcquire(arg)
// && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// selfInterrupt();
// 如果acquireQueued(addWaiter(Node.EXCLUSIVE), arg))返回true的话,
// 此时将进入selfInterrupt()
// 所以正常情况下,acquireQueued(addWaiter(Node.EXCLUSIVE), arg))应该返回false
// 继续跟进看看吧
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// p == head 说明当前节点是阻塞队列的第一个,因为它的前驱是head
// 我的理解是阻塞队列其实并不包含head节点
// head一般指的是占有锁的线程,head后面的才称为阻塞队列(个人理解,勿喷)
// 所以这个当前节点其实可以去尝试抢一下锁
// 为什么可以尝试去抢一下锁呢
// 首先,它是队头,其次,当前的head有可能是刚刚初始化的node,
// enq(node) 方法里面有提到,head是延时初始化的,而且new Node()的时候没有设置任何线程
// 也就是说,当前的head不属于任何一个线程,所以作为队头,可以去试一试,
// tryAcquire已经分析过了, 忘记了请往前看一下,就是简单用CAS试操作一下state
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 到这里,说明上面的if分支没有成功,要么当前node本来就不是队头,
// 要么就是tryAcquire(arg)没有抢赢别人,继续往下看
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 什么时候 failed 会为 true???
// tryAcquire() 方法抛异常的情况
if (failed)
cancelAcquire(node);
}
}
// 会到这里就是没有抢到锁,判断是否需要挂起当前线程
// 第一个参数是前驱节点,第二个参数才是代表当前线程的节点
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 进入阻塞队列排队的线程会将他的前驱节点设为Node.SIGNAL
// 也就是说如果自己可以抢锁了就让前面的节点提醒自己,自己则被挂起等待唤醒
// 前驱节点的 waitStatus == -1 ,说明前驱节点状态正常,可以直接返回
if (ws == Node.SIGNAL)
return true;
// 前驱节点 waitStatus大于0 ,说明前驱节点取消了排队。
// 前面的人都取消排队了,但是你还得依赖它来唤醒呢,
// 往前遍历总能找到一个没有取消排队的节点,来提醒自己
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
// 到了这里说明前驱节点的waitStatus不等于Node.SIGNAL和CANCEL
// 那也就是只可能是0,-2,-3
// 都没有看到有设置waitStatus的,所以每个新的node入队时,waitStatu都是0
// 用CAS将前驱节点的waitStatus设置为Node.SIGNAL(也就是-1),让前面的提醒自己
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
// 这个方法返回 false,那么会再走一次 for 循序,
// 然后再次进来此方法,此时会从第一个分支返回 true
return false;
}
// if (shouldParkAfterFailedAcquire(p, node) &&
// parkAndCheckInterrupt())
// interrupted = true;
// 如果shouldParkAfterFailedAcquire(p, node)返回true,
// 就会执行parkAndCheckInterrupt():
// 这个方法很简单,因为前面返回true,所以需要挂起线程,这个方法就是负责挂起线程的
// 这里用了LockSupport.park(this)来挂起线程,然后就停在这里了,等待被唤醒=======
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
// 为什么shouldParkAfterFailedAcquire(p, node)返回false的时候不直接挂起线程:
// 是为了应对在经过这个方法后,node已经是head的直接后继节点了,还需要挂起干嘛,直接可以尝试抢锁了。
}
解锁操作(公平锁FairLock)
// 解锁操作相比加锁还是比较简单的
public void unlock() {
sync.release(1);
}
//没什么说的,继续
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
// 回到ReentrantLock看tryRelease方法
protected final boolean tryRelease(int releases) {
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;
}
// 唤醒后继节点
// 从上面调用处知道,参数node是head头结点
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
// 如果head节点当前waitStatus<0, 将其修改为0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 下面的代码就是唤醒后继节点,但是有可能后继节点取消了等待(waitStatus==1)
// 从队尾往前找,找到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);
}
//这个时候线程被唤醒的时候是在这里
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
//这里被唤醒了,然后继续检查前驱是不是头节点,尝试获取锁
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
至此公平锁下的加锁操作和解锁操作就都解析完了
公平锁总结
公平锁主要注意的就是下面三个方面
- 锁状态。成员变量 “state”,它为 0 的时候代表没有线程占有锁,可以去争抢这个锁,用 CAS 将 state 设为 state + args,如果 CAS 成功,说明抢到了锁,这样其他线程就抢不到了,如果锁重入的话,state进行 +1 就可以,解锁就是减 1,直到 state 又变为 0,代表释放锁。
- 线程的阻塞和解除阻塞。AQS 中采用了 LockSupport.park() 来挂起线程,用 LockSupport.unpark() 来唤醒线程。
- 阻塞队列。因为争抢锁的线程可能很多,但是只能有一个线程拿到锁,其他的线程都必须等待,这个时候就需要一个 足赛队列 来管理这些线程节点,AQS 用的其实就是一个链表队列,每个 node 都持有后继节点和前驱节点的引用,大家都在里面公平的排队等待加锁,只有排到了第一个才有资格去加锁。
加锁操作(非公平锁 NonFairLock)
上面解析了公平锁和非公平锁的加解锁,接下来是非公平锁,其实公平锁理解了,非公平锁就很好理解了还是先贴上代码吧
public ReentrantLock() {
// 默认非公平锁
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
非公平锁的 lock 方法:
static final class NonfairSync extends Sync {
final void lock() {
// 2. 和公平锁相比,这里会直接先进行一次CAS,成功就返回了
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
// AbstractQueuedSynchronizer.acquire(int arg)
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
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;
}
其实从代码就可以看出公平锁和非公平锁的区别了:
非公平锁在调用 lock 后,首先就会调用 CAS 进行一次抢锁,非公平锁在 CAS 失败后,和公平锁一样都会进入到 tryAcquire 方法,在
tryAcquire 方法中,如果发现锁这个时候被释放了(state == 0),非公平锁会直接 CAS 抢锁,但是公平锁会判断等待队列是否有线程
在排队等待加锁,如果有则不会抢锁而是去队列里排队。
如果这两次 CAS 都不成功,那么后面非公平锁和公平锁是一样的,都要进入到阻塞队列等待唤醒。
相对来说,非公平锁会有更好的性能,因为它的吞吐量比较大。当然,非公平锁让获取锁的时间变得更加不确定,可能会导
致在阻塞队列中的线程长期处于饥饿状态,所以ReentrantLock默认是非公平锁。
并发基础之——Condition
Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式,这也是它常用的应用场景。
public class Demo {
private Lock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
private List<Object> produceItems = new LinkedList<Object>();
private int itemCount = 0;
private int fullCount = 50;
public void produce(Object obj) {
lock.lock();
try {
while (fullCount == produceItems.size()) {
notFull.await();
}
produceItems.add(obj);
System.out.println(Thread.currentThread().getName() + "生产了,当前数量" +produceItems.size());
notEmpty.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void consume() {
lock.lock();
try {
while (produceItems.size() == 0) {
notEmpty.await();
}
//Ignor rmove(0)
produceItems.remove(0);
System.out.println(Thread.currentThread().getName() + "消费了,当前数量" +produceItems.size());
notFull.signal();
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
但是这两者在使用方式以及功能特性上还是有差别的。Object和Condition接口的一些对比
Condition的定义如下
public interface Condition {
void await() throws InterruptedException;
boolean await(long time, TimeUnit unit) throws InterruptedException;
void awaitUninterruptibly();
long awaitNanos(long nanosTimeout) throws InterruptedException;
boolean awaitUntil(Date deadline) throws InterruptedException;
void signal();
void signalAll();
}
Condition的实现JAVA8中有两个
这里分析的是AbstractQueuedSynchronizer 中的 ConditionObject,主要的属性如下
public class ConditionObject implements Condition, java.io.Serializable {
// 条件队列的第一个节点
private transient Node firstWaiter;
// 条件队列的最后一个节点
private transient Node lastWaiter;
// AQS有一个阻塞队列,用于保存等待获取锁的线程的队列。
// 这里引入了另一个概念,叫条件队列(condition queue)
AQS有一个阻塞队列,用于保存等待获取锁的线程的队列;这里引入了另一个概念,叫条件队列(condition queue)
阻塞队列和条件队列的异同和作用这里先说明,后面再根据源码论述
- 条件队列和阻塞队列的节点,都是 Node 的实例,因为条件队列的节点是需要转移到阻塞队列中去的;
- 一个 ReentrantLock 实例可以通过多次调用 newCondition() 来产生多个 Condition 实例,这里对应 condition1 和 conditionN并且ConditionObject 只有两个属性 firstWaiter 和 lastWaiter;
- 每个 condition 有一个关联的条件队列,如线程 1 调用
condition1.await()
方法即可将当前线程 1 包装成 Node 后加入到条件队列中,然后阻塞在这里,不继续往下执行,条件队列是一个单向链表; - 调用
condition1.signal()
触发一次唤醒,此时唤醒的是队头,会将condition1 对应的条件队列的 firstWaiter(队头) 移到阻塞队列的队尾,等待获取锁,获取锁后 await 方法才能返回,继续往下执行。
Condition的await方法大致逻辑
// 这个方法会阻塞,直到调用 “唤醒”方法,或线程被中断
public final void await() throws InterruptedException {
// 方法要响应中断,在最开始就判断中断状态
if (Thread.interrupted())
throw new InterruptedException();
// 添加到 condition 的条件队列中
Node node = addConditionWaiter();
// await() 之前,当前线程是必须持有锁的,这里肯定要释放掉
int savedState = fullyRelease(node);
int interruptMode = 0;
// 这里退出循环有两种情况,之后再仔细分析
// 1. isOnSyncQueue(node) 返回 true,即当前 node 已经转移到阻塞队列了
// 2. checkInterruptWhileWaiting(node) != 0 会到 break,然后退出循环,代表的是线程中断
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 被唤醒后,将进入阻塞队列,等待获取锁
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null)
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
添加到 condition 的条件队列中
private Node addConditionWaiter() {
Node t = lastWaiter;
// 如果最后一个节点取消排队了,将它清除出去
if (t != null && t.waitStatus != Node.CONDITION) {
unlinkCancelledWaiters();
t = lastWaiter;
}
//将线程包装成一个NOde,指定 它的 waitStatus 为 Node.CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
//清除取消排队的节点
//这个代码要怎么说呢,没什么好说的多看几遍就懂了。。。。
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
至此线程已经被包装成了一个waitStatus为 Node.CONDITION的 Node对象并且进入了条件队列,回到await方法,第二步fullyRealease
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
//解锁
if (release(savedState)) {
failed = false;
return savedState;
} else {
throw new IllegalMonitorStateException();
}
} finally {
//这里解释一下为什么要这个操作
//有这么一种场景;有一个线程不按套路出牌,没有抢到锁就直接调用await方法
//那么在这里它走到解锁的时候肯定会到上面的else分支从而failed为true
//这里就会把它设为取消排队,在遍历清除取消节点的时候就会把它清除出去
if (failed)
node.waitStatus = Node.CANCELLED;
}
}
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//从阻塞队列的队头一直往后找
//直到找到一个没有取消排队的节点唤醒它(因为这个时候它可以抢锁了)
unparkSuccessor(h);
return true;
}
return false;
}
接下来是这段,正常情况下,已经进入了条件队列,这时通过自旋,如果发现自己还没到阻塞队列,那么挂起,等待被转移到阻塞队列。
if (Thread.interrupted())
throw new InterruptedException();
Node node = addConditionWaiter();
int savedState = fullyRelease(node);
int interruptMode = 0;
//已经到达了这里
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
final boolean isOnSyncQueue(Node node) {
// 如果 waitStatus 还是 Node.CONDITION,也就是 -2,那肯定就是还在条件队列中
// 因为移到阻塞队列的时候,node 的 waitStatus 会置为 0 (这一步在signal的时候,后续会说)
// 如果 node 的前驱还是 null,说明肯定没有在阻塞队列(prev是阻塞队列链表中使用的)
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 如果 node 已经有后继节点 next 的时候,那肯定是在阻塞队列了
if (node.next != null)
return true;
// 下面这个方法看名字就大概能猜到从阻塞队列队尾查找Node,找到了说明已经转移到阻塞队列了
return findNodeFromTail(node);
}
// 从阻塞队列的队尾往前遍历,如果找到,返回 true
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
isOnSyncQueue(node) 返回 false 的话,那么进到 LockSupport.park(this);
这里线程挂起,如果线程挂起的话,怎么才会唤醒线程继续走下去呢,答案就是调用sinal相关的方法
public final void signal() {
// 调用 signal 方法的线程必须持有当前的独占锁
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//去唤醒条件队列的队头节点,因为它等得最久
Node first = firstWaiter;
if (first != null)
doSignal(first);
}
protected final boolean isHeldExclusively() {
return getExclusiveOwnerThread() == Thread.currentThread();
}
private void doSignal(Node first) {
do {
// 将 firstWaiter 指向 first 节点后面的第一个,因为 first 节点马上要离开了
// 如果除了first没有其他线程在条件队列了,那么需要将 lastWaiter 置为 null
if ( (firstWaiter = first.nextWaiter) == null)
lastWaiter = null;
//断绝和条件队列的关联关系
first.nextWaiter = null;
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
final boolean transferForSignal(Node node) {
//如果cas改变waitStatus状态失败了,说明该节点取消了
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//自旋配合CAS进入阻塞队列的队尾
Node p = enq(node);
int ws = p.waitStatus;
// 前驱节点取消排队了,提前唤醒线程
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
这里signal方法的逻辑就完成了,后面就是线程被唤醒后的操作了,有以下三种情况会让线程唤醒继续往下执行:
- 常规路径。signal -> 转移节点到阻塞队列 -> 获取了锁(unpark)
- 线程中断。在 park 的时候,另外一个线程对这个线程进行了中断
- signal 的时候我们说过,转移以后的前驱节点取消了,或者对前驱节点的CAS操作失败了
while (!isOnSyncQueue(node)) {
//开始我们的线程停在了这里等待唤醒
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
//线程唤醒后会去调用checkInterruptWhileWaiting(),
//这个方法的作用是判断是否在线程挂起期间发生了中断
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
//如果线程发生了中断就会进入transferAfterCancelledWait()方法,这个方法是干嘛用的,继续看
final boolean transferAfterCancelledWait(Node node) {
// 用 CAS 将节点状态设置为 0
// 如果这步 CAS 成功,说明是 signal 方法之前发生的中断,因为signal的时候会将 waitStatus 设置为 0
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
// 将节点放入阻塞队列,即使中断了,依然会转移到阻塞队列
enq(node);
return true;
}
// 到这里是因为 CAS 失败,肯定是因为 signal 方法已经将 waitStatus 设置为了 0
// signal 方法会将节点转移到阻塞队列,但是可能还没完成,这里自旋等待完成
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
//while 循环出来后,已经确定节点已经进入了阻塞队列
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//acquireQueued方法,就是最开始加锁的逻辑了
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//自己的前驱是不是head是的话可以尝试加锁了
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
//这里会检查线程是否被中断,中断过的都会返回true
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
//挂起线程并且返回线程是否被中断过
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
继续往下看就剩两个方法了
//signal的时候会将节点转移到阻塞队列,有一步是 node.nextWaiter = null 断开节点和条件队列的联系。
//这里有一种情况就是在signal之前就中断过了,并且我们知道中断过了也会转移到阻塞队列
//这个时候是没有设置node.nextWaiter = null
//如果有节点取消,就是在这里调用清除取消排队节点
if (node.nextWaiter != null)
unlinkCancelledWaiters();
//0:什么都不做,没有被中断过;
//THROW_IE:await 方法抛出 InterruptedException 异常,因为它代表在 await() 期间发生了中断;
//REINTERRUPT:重新中断当前线程,因为它代表 await() 期间没有被中断,而是 signal() 以后发生的中断
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);