还不知道ReentrantLock的实现流程,那你就out了( 二 )

关于await , 有几个关键点要说明:

  1. 线程调用await()的时候 , 肯定已经先拿到锁 。所以 , 在addConditionWaiter()内部 , 对这个双向链表的操作不需要执行cas操作 , 线程天生是安全的 , 代码如下:
private Node addConditionWaiter() {// ...//拿到末端的nodeNode t = lastWaiter;//new一个nodeNode node = new Node(Thread.currentThread(), Node.CONDITION);if (t == null)firstWaiter = node;elset.nextWaiter = node;lastWaiter = node;return node;}复制代码
  1. 在线程执行wait操作之前 , 必须先释放锁 。也就是fullyRelease(node) , 否则会发生死锁 。这个和wait/notify与synchronized的配合机制一样
  2. 线程从wait中被唤醒后 , 必须用acquireQueue(node,savedState)方法重新拿锁 。
  3. checkInterruptWhileWaiting(node)代码在park(this)代码之后 , 是为了检测在park期间是否收到过中断信号 。当线程从park中醒来时 , 有两种可能:一种是其他线程调用了unpark , 另一种是收到中断信号 。这里的await()方法是可以响应中断的 , 所以当发现自己是被中断唤醒的 , 而不是被unpark唤醒的时候 , 会直接退出while循环 , await()方法也会返回 。
  4. isOnSyncQueue(node)用于判断该Node是否在AQS的同步队列里面 。初始的时候 , Node只在Condition的队列里 , 而不在AQS的队列里 。但执行notity操作的时候 , 会放进AQS的同步队列
awaitUninterruptibly实现分析与await()不同 , awaitUninterruptibly()不会响应中断 , 其方法的定义中不会有中断异常抛出 , 下面分析实现和await()的区别
public final void awaitUninterruptibly() {Node node = addConditionWaiter();int savedState = fullyRelease(node);boolean interrupted = false;while (!isOnSyncQueue(node)) {LockSupport.park(this);if (Thread.interrupted())interrupted = true;}if (acquireQueued(node, savedState) || interrupted)selfInterrupt();}复制代码可以看出 , 整体代码和await()类似 , 区别在于收到异常后 , 不会抛出异常 , 而是继续执行while循环
Condition.signal
调用Condition的signal方法 , 将会唤醒在等待队列中等待时间最长的节点(首节点) , 在唤醒节点之前 , 会将节点移到同步队列中
public final void signal() {if (!isHeldExclusively())//先判断当前线程是否获取了锁throw new IllegalMonitorStateException();Node first = firstWaiter;//拿到Condition队列上第一个节点if (first != null)doSignal(first);}复制代码Condition.doSignal
private void doSignal(Node first) {do {if ( (firstWaiter = first.nextWaiter) == null)//如果第一个节点的下一个节点是null , 那么 , 最后一个节点也是nulllastWaiter = null;//将next节点设置成nullfirst.nextWaiter = null;} while (!transferForSignal(first) &&(first = firstWaiter) != null);}复制代码【还不知道ReentrantLock的实现流程,那你就out了】final boolean transferForSignal(Node node) {Node p = enq(node);int ws = p.waitStatus;//如果上一个节点的状态被取消了 , 或者尝试设置上一个节点的状态为SIGNAL失败了(SIGNAL 表示: 他的 next 节点需要停止阻塞)if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))LockSupport.unpark(node.thread);//唤醒输入节点上的线程return true;}复制代码该方法先是CAS修改了节点状态 , 如果成功 , 就将这个节点放到AQS队列中 , 然后唤醒这个节点上的线程 。此时 , 那个节点就会在await方法中苏醒
Condition总结阻塞:await()方法中 , 在线程释放锁资源之后 , 如果节点不在 AQS 等待队 列 , 则阻塞当前线程 , 如果在等待队列 , 则自旋等待尝试获取锁
释放:signal()后 , 节点会从 condition 队列移动到 AQS 等待队列 , 则进入 正常锁的获取流程
作者:Five在努力
链接:https://juejin.cn/post/6918557057947795470




推荐阅读