4.3.5.3 数据结构
前驱节点
- 链接到当前节点/线程所依赖的用来检查 waitStatus 的前驱节点
- 在入队期间赋值,并且仅在出队时将其清空(为了GC)。
此外,在取消一个前驱结点后,在找到一个未取消的节点后会短路,这将始终存在,因为头节点永远不会被取消:只有成功 acquire 后,一个节点才会变为头。
取消的线程永远不会成功获取,并且线程只会取消自身,不会取消任何其他节点。
后继节点
链接到后继节点,当前节点/线程在释放时将其unpark。 在入队时赋值,在绕过已取消的前驱节点时进行调整,在出队时置null(为了GC)。
入队操作直到附加后才赋值前驱节点的next
字段,因此看到next
字段为 null,并不一定意味该节点位于队尾(有时间间隙)。
但若next == null
,则可从队尾开始扫描prev
以进行再次检查。
// 若节点通过从tail向前搜索发现在在同步队列上,则返回 true // 仅在调用了 isOnSyncQueue 且有需要时才调用 private boolean findNodeFromTail(Node node) { Node t = tail; for (;;) { if (t == node) return true; if (t == null) return false; t = t.prev; } }
final boolean isOnSyncQueue(Node node) { if (node.waitStatus == Node.CONDITION || node.prev == null) return false; if (node.next != null) // If has successor, it must be on queue return true; /** * node.prev 可以非null,但还没有在队列中,因为将它放在队列中的 CAS 可能会失败。 * 所以必须从队尾向前遍历以确保它确实成功了。 * 在调用此方法时,它将始终靠近tail,并且除非 CAS 失败(这不太可能) * 否则它会在那里,因此几乎不会遍历太多 */ return findNodeFromTail(node); }
已取消节点的next
字段设置为指向节点本身而不是null,以使isOnSyncQueue更轻松。
使该节点入队的线程。 在构造时初始化,使用后消亡。
在同步队列中,nextWaiter 表示当前节点是独占模式还是共享模式
在条件队列中,nextWaiter 表示下一个节点元素
链接到在条件队列等待的下一个节点,或者链接到特殊值SHARED。 由于条件队列仅在以独占模式保存时才被访问,因此我们只需要一个简单的链接队列即可在节点等待条件时保存节点。 然后将它们转移到队列中以重新获取。 并且由于条件只能是独占的,因此我们使用特殊值来表示共享模式来保存字段。
5 Condition 接口
JDK5 时提供。
- 条件队列 ConditionObject 实现了 Condition 接口
- 本节就让我们一起来研究之
- Condition 将对象监视方法(wait,notify和notifyAll)分解为不同的对象,从而通过与任意Lock实现结合使用,从而使每个对象具有多个wait-sets。 当 Lock 替换了 synchronized 方法和语句的使用,Condition 就可以替换了Object监视器方法的使用。
Condition 的实现可以提供与 Object 监视方法不同的行为和语义,例如保证通知的顺序,或者在执行通知时不需要保持锁定。 如果实现提供了这种专门的语义,则实现必须记录这些语义。
Condition实例只是普通对象,它们本身可以用作 synchronized 语句中的目标,并且可以调用自己的监视器 wait 和 notification 方法。 获取 Condition 实例的监视器锁或使用其监视器方法与获取与该条件相关联的锁或使用其 await 和 signal 方法没有特定的关系。 建议避免混淆,除非可能在自己的实现中,否则不要以这种方式使用 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; ++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; --count; notFull.signal(); return x; } finally { lock.unlock(); } } }
(ArrayBlockingQueue类提供了此功能,因此没有理由实现此示例用法类。)
定义出一些方法,这些方法奠定了条件队列的基础
API
await
- 使当前线程等待,直到被 signalled 或被中断
- 与此 Condition 相关联的锁被原子释放,并且出于线程调度目的,当前线程被禁用,并且处于休眠状态,直到发生以下四种情况之一:
其它线程为此 Condition 调用了 signal 方法,并且当前线程恰好被选择为要唤醒的线程
其它线程为此 Condition 调用了 signalAll 方法
其它线程中断了当前线程,并且当前线程支持被中断
发生“虚假唤醒”。
在所有情况下,在此方法可以返回之前,必须重新获取与此 Condition 关联的锁,才能真正被唤醒。当线程返回时,可以保证保持此锁。
await 超时时间
- 使当前线程等待,直到被 signal 或中断,或经过指定的等待时间
- 此方法在行为上等效于:
awaitNanos(unit.toNanos(time)) > 0
所以,虽然入参可以是任意单位的时间,但其实仍会转化成纳秒
awaitNanos
注意这里选择纳秒是为了避免计算剩余等待时间时的截断误差
signal()
- 唤醒条件队列中的一个线程,在被唤醒前必须先获得锁
signalAll()
- 唤醒条件队列中的所有线程