接上一篇:源码解析 ReentrantLock
1. await()
查看 ReentrantLock.newCondition() 的 await() 方法
void await() throws InterruptedException;
这是一个接口方法,实现者是 AbstractQueuedSynchronizer 的 await()
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
//往条件等待队列中,插入一个条件等待节点(内含当前线程)
Node node = addConditionWaiter();
//尝试释放锁,返回值:表示之前被加锁次数(可重入锁可以被多次加锁),一般为 1
int savedState = fullyRelease(node);
int interruptMode = 0;
//判断是否位于同步队列中,如果不是,不断挂起线程,直到进入同步队列
while (!isOnSyncQueue(node)) {
//使用当前 AbstractQueuedSynchronizer 对象,挂起当前线程
LockSupport.park(this);
//在条件等待过程中,判断是否线程中断,是的话跳出循环,结束等待
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
//checkInterruptWhileWaiting(node) 很有趣:如果当前线程已中断,判断
//(1) 位于 signal 释放条件信号之前,将当前节点从条件等待队列移入同步队列
//(2) 位于 signal 释放条件信号之后,如果同步队列中不存在当前节点(signal() 方法不仅能改变条件等待状态,同时还会负责将节点移入同步队列),
//呢么将永远让线程调度器无视掉当前线程
}
//acquireQueued 进入队列无限等待,直到获得锁,如果返回 true,表示线程被中断了
//具体注释在:《源码解析 ReentrantLock》
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
//如果存在后继条件等待节点,尝试清除所有 “取消状态” 的节点
//这里的策略是从第一个条件等待节点开始,向后遍历,依次清除 “取消状态” 的节点,具体请看源码
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
//判断当前线程是否被中断
if (interruptMode != 0)
//根据情况,选择 (1)抛出中断异常 (2)手动中断当前线程
reportInterruptAfterWait(interruptMode);
//如果到达此处,表示满足条件,业务代码继续向下执行
}
查看 addConditionWaiter(),了解如何将线程插入条件等待队列
private Node addConditionWaiter() {
Node t = lastWaiter;
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
//如果最后一个条件等待节点已经被取消了,先清除它
//这里的策略是从第一个条件等待节点开始,向后遍历,依次清除 “取消状态” 的节点,具体请看源码
unlinkCancelledWaiters();
//在执行清除后,重新获取最后一个条件等待节点
t = lastWaiter;
}
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)
//t 为 null,表示队列为空,于是设置头结点,初始化队列
firstWaiter = node;
else
t.nextWaiter = node;
lastWaiter = node;
return node;
}
查看 fullyRelease(node),了解当条件不满足时,释放锁的工作内容
final int fullyRelease(Node node) {
//失败标志,默认为 true
boolean failed = true;
try {
//获取同步状态值,>0 表示锁资源被占用,=N 表示被加锁 N 次
int savedState = getState();
//尝试释放锁
if (release(savedState)) {
//释放锁成功
failed = false;
return savedState;
} else {
//释放锁失败,抛错,因为释放锁的方法里还包括了唤醒挂起线程,如果失败了就全线程阻塞了
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
//如果整个流程失败了,将当前节点设置为 “取消状态”
node.waitStatus = Node.CANCELLED;
}
}
//......
public final boolean release(int arg) {
//尝试释放锁,具体代码在下边
if (tryRelease(arg)) {
//到达此处表示,释放锁成功
Node h = head;
//如果 h 有效,唤醒 h 的有效后继者(即 “非取消状态” 的后继节点)
//可以简单理解为:队列先进先出,锁(系统)资源分配给下一个线程节点
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
/*注意:这是 ReentrantLlock.Sync.tryRelease() 方法,与上边方法不在同一个类 */
protected final boolean tryRelease(int releases) {
//将同步状态-1(如果同步状态=3,表示由同一线程加锁 3 次)
int c = getState() - releases;
//如果当前线程不是 “独占线程”,抛出错误
//举个例子:如果一个线程明明没有获得锁,却尝试释放锁,就会触发该异常
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
//表示是否应该释放锁,因为有时候同一个锁可能被加锁多次,一次释放是不够的
boolean free = false;
//同步状态=0,表示无任何线程占有锁,锁(系统)资源是自由的
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
2. signalAll()
查看 ReentrantLock.newCondition() 的 signalAll() 方法
public final void signalAll() {
//如果当前线程不是 “独占线程”,抛出错误
//举个例子:如果一个线程明明没有获得锁,却尝试释放锁,就会触发该异常
//感觉此处别有用意,个人猜测:强制开发者将 signalAll() 放在 unlock() 代码之前
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
//获取第一个条件等待节点
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
//...
private void doSignalAll(Node first) {
//删除条件等待队列
lastWaiter = firstWaiter = null;
do {
//遍历整个条件等待队列
Node next = first.nextWaiter;
first.nextWaiter = null;
//发送一个信号,将其从条件等待队列移入同步队列
//如此一来,该线程就脱离了条件等待,进入同步队列去获取锁
transferForSignal(first);
first = next;
} while (first != null);
}
//...
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//移入同步队列
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}