Condition condition = lock.newCondition();
await()
实现可中断条件等待。
1如果当前线程被中断,则抛出 InterruptedException。
2保存由getState返回的锁状态。
3使用保存的状态作为参数调用release ,如果失败则抛出 IllegalMonitorStateException。
4阻塞直到发出信号或被中断。
5通过以保存的状态作为参数调用专门版本的acquire 。
6如果在步骤 4 中被阻塞时被中断,则抛出 InterruptedException
public final void await() throws InterruptedException {
`一进来就设置中断标志 如果已经设置了 就抛出异常`
if (Thread.interrupted())
throw new InterruptedException();
创建一个结点并清除垃圾节点 然后 添加到队列的尾部 返回当前创建的结点
Node node = addConditionWaiter();
完全释放当前线程所持有的锁 就是unlock的过程 移除结点 唤醒等待队列里面的一个结点 主要针对重入锁 释放锁前的 state 值
int savedState = fullyRelease(node);
int interruptMode = 0;
// 判断节点是否在同步队列中 此时有可能被single 唤醒 cas可能失败 同步队列从后往前遍历同步队列也减少时间
// 当没有在同步队列里面 或者说 被唤醒的时候 则将当前线程阻塞
while (!isOnSyncQueue(node)) {
// 因为前面只是释放锁 当前线程还在运行 就需要暂停当前线程
// 一直暂停中 ... 直到被唤醒
LockSupport.park(this);
// 只有unpark 或者 中断的时候才会执行
// =0 的情况说明既没有被唤醒 也没有被中断打断, park -1 是 中断返回值, 1 是 single 和 中断导致线程运行 竞争成功
// 只有在中断的情况下才会跳出这个循环
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
// 重新尝试获取锁 就是 lock的过程 而且是公平锁的调用方法 而且 只能在获取到锁之后响应中断
1如果一下子获取到锁之后 返回false 往下走
2或者 获取锁失败又在这里park 住了 ... 直到竞争到锁 返回 true 并且判断 当前的中断模式是不是 抛出异常
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
删除垃圾节点 也就是当前线程
unlinkCancelledWaiters();
if (interruptMode != 0)
// 再次响应中断 根据模式 抛出异常 或者 这只中断标志位
reportInterruptAfterWait(interruptMode);
}
添加一个新的 等待节点 等待队列 是 双链表结构 但只用到了 单链表
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)
如果尾结点== null 说明这个队列是空的 所以 头结点尾结点都是这个新创建的node
firstWaiter = node;
else
设置尾结点 为 新创建的node
t.nextWaiter = node;
最后一个节点就是当前创建的结点
lastWaiter = node;
返回
return node;
}
从条件队列中取消链接已取消的等待节点。 '仅在持有锁时调用'。 await 肯定持有锁
当在条件等待期间发生取消时,以及在看到 lastWaiter 已被取消时插入新的服务员时,将调用此方法。
'需要这种方法来避免在没有信号的情况下垃圾保留'。 因此,即使它可能需要完全遍历,它也仅在没有信号的情况下发生超时或取消时才起作用。
"它遍历所有节点而不是在特定目标处停止以" "取消所有指向垃圾节点的指针的链接",而无需在取消风暴期间进行多次重新遍历。
private void unlinkCancelledWaiters() {
// 头结点
Node t = firstWaiter;
Node trail = null;
// 遍历链表
while (t != null) {
Node next = t.nextWaiter;
// 判断 t 是否垃圾结点
if (t.waitStatus != Node.CONDITION) {
是垃圾结点
断开连接
t.nextWaiter = null;
if (trail == null)
// 将 头结点 设置为 下一个
firstWaiter = next;
else
// 删除 t 结点 只用到了单链表 直接删除就可以
trail.nextWaiter = next;
if (next == null)
// 下一个结点 == null 则设置尾结点就是trail
lastWaiter = trail;
}
else
不是垃圾结点 头还是t
trail = t;
更新t
t = next;
}
}
使用当前状态值调用 release; 返回保存状态。 取消节点并在失败时抛出异常。
参数:
node – 此等待的条件节点
返回:
之前的同步状态
final int fullyRelease(Node node) {
boolean failed = true;
try {
获取当前线程的状态
int savedState = getState();
当前线程解锁 `从线程的队列中移除` 唤醒其他线程
if (release(savedState)) {
failed = false;
return savedState;
} else {
// 失败的话抛异常
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
并且将当前 线程的状态设置成 等待 ...
node.waitStatus = Node.CANCELLED;
}
}
如果一个节点(始终是最初放置在条件队列中的节点)现在正在等待重新获取同步队列,则返回 true。
参数:
节点——节点
返回:
如果重新获取则为真
// 判断是否在同步队列里面 因为有可能被唤醒 single
final boolean isOnSyncQueue(Node node) {
// 当前结点是 等待状态 || 当前结点是头结点 返回false 为什么要判断前结点是否为null 是因为 唤醒过程中要执行两个操作 set prev 和 cas 设置前一个结点的后一个结点是 当前结点 ,cas 可能失败 前一个一定不会
// 如果当前结点没有被唤醒 就 return false
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
// 他都有后结点了 说明他一定在同步队列里面 2333 prev 和 next 针对的都是同步队列
if (node.next != null) // If has successor, it must be on queue
return true;
/*
* node.prev can be non-null, but not yet on queue because
* the CAS to place it on queue can fail. So we have to
* traverse from tail to make sure it actually made it. It
* will always be near the tail in calls to this method, and
* unless the CAS failed (which is unlikely), it will be
* there, so we hardly ever traverse much.
node.prev 可以是非空的,但还没有在队列中,因为将它放入队列的 CAS 可能会失败。所以我们必须从尾部遍历以确保它确实做到了。在调用这个方法时它总是靠近尾部,除非 CAS 失败(这不太可能),它会在那里,所以我们几乎不会遍历太多。
*/
// 判断当前结点是否在同步队列里面 倒着往前是因为cas 可能出现异常
return findNodeFromTail(node);
}
如果节点通过从尾部向后搜索在同步队列上,则返回 true。 仅在 isOnSyncQueue 需要时调用。
返回:
如果存在则为真
倒着往上找同步队列是否存在当前结点
private boolean findNodeFromTail(Node node) {
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
检查是否在等待的时候中断
如果没有设置过中断就直接返回 0
如果设置了中断 并且和 single竞争成功就返回 THROW_IE = -1
如果和 single 竞争失败就返回 REINTERRUPT = 1
private int checkInterruptWhileWaiting(Node node) {
// 如果当前线程被打上中断标记 并复位 或者设置为中断
// 设置中断会打断park()
return Thread.interrupted() ?
// 把他添加到同步队列中 模式由中断设为正常
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) : 0;
}
如有必要,在取消等待后将节点传输到同步队列。 如果线程在发出信号之前被取消,则返回 true。
参数:
节点——节点
返回:
如果在节点收到信号之前取消,则为 true
// cas 成功 添加到同步队列里面了
该方法的返回值代表当前线程是否在park的时候被中断唤醒,
如果为true表示中断在signal调用之前,signal还未执行,那么这个时候会根据await的语义,在await时遇到中断需要抛出interruptedException,返回true就是告诉checkInterruptWhileWaiting返回THROW_IE(-1)
`能走到这个方法 只能 是 调用中断方法 打断了 park`
final boolean transferAfterCancelledWait(Node node) {
// 将节点由等待结点 设置为正常模式的结点
`所以在此cas 失败的唯一可能就是 与 single竞争失败 `
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
// 然后添加到同步队列里面
enq(node);
`竞争成功 或者 没有竞争 就返回true 然后后面的 返回结果就是 当前线程已经被中断过一次了 下次再中断 就要抛异常了 `
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
如果我们输给了一个 signal(),那么在它完成它的 enq() 之前我们不能继续。在不完整的传输过程中取消既罕见又短暂,因此只需旋转即可
*/
while (!isOnSyncQueue(node))
Thread.yield();
return false;
}
根据模式设置当前线程是中断 还是抛出异常
为什么要区分这两种模式 就是因为 这个代码能执行到这里一定是因为 执行中断 才会走到这里 需要重新设置中断标志 要么中断和 single竞争成功要么竞争失败
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE)
等待过程中 请求中断 会把等待队列中的 当前线程添加到同步队列中 并在获取到锁的时候 抛出个异常
throw new InterruptedException();
else if (interruptMode == REINTERRUPT)
重新响应中断 single 赢了的情况
selfInterrupt();
}
single()
将等待时间最长的线程(如果存在)从此条件的等待队列移动到拥有锁的等待队列。
public final void signal() {
// 如果是当前线程持有这把锁 则唤醒其他线程
// 这是因为只有持有锁的 线程才能唤醒
if (!isHeldExclusively())
// 不是当前线程抛出异常
throw new IllegalMonitorStateException();
// 获取第一个结点
Node first = firstWaiter;
// 如果结点不等于空
if (first != null)
doSignal(first);
}
删除并转移节点,直到命中未取消的一或空。 从信号中分离出来部分是为了鼓励编译器内联没有服务员的情况。
参数:
first –(非空)条件队列中的第一个节点
把所有的等待队列中的结点都拿出来 到 同步队列
private void doSignal(Node first) {
do {
// 把头结点的下一个给头结点 如果为空的时候
if ( (firstWaiter = first.nextWaiter) == null)
// 尾结点设为空
lastWaiter = null;
// 头结点的下一个 = null 就是 断开联系了
first.nextWaiter = null;
// 把头结点 唤醒加到同步队列中
// 只唤醒一个 完了就结束 over
} while (!transferForSignal(first) &&
(first = firstWaiter) != null);
}
将节点从条件队列转移到同步队列。 如果成功则返回真。
参数:
节点——节点
返回:
如果成功传输则为真(否则节点在信号之前被取消)
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
* 在这里cas 和 中断 抢 看谁成功
* 如果失败的话 直接返回false
*/
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).
*/
cas 成功的情况 添加到同步队列中 并返回一个p结点
Node p = enq(node);
// 前一个结点的状态 > 0 或者 把前一个状态设置成 -1 就是需要唤醒后面的线程
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
// 当前线程唤醒 时间最久的也就是头结点
LockSupport.unpark(node.thread);
return true;
}