AQS之Condition源码分析总结

参考原文链接:https://blog.csdn.net/anlian523/article/details/106653034

Condition源码分析

Condition队列和Sync队列比较

  • condition队列是个单向链表,但是Sync是个双向链表。
  • condition入队是有锁的(准备释放),但是Sync入队是无锁的
  • condition出队是无锁的,Sync出队是因为获取到锁
  • 因为conditionObject是AQS的内部类,所以迁移起来很方便,可以通过AQS的方法直接来进行把队列进行位置迁移。Sync实现了AQS可以做到这样的事
public abstract class AbstractQueuedSynchronizer{
    
    
	private transient volatile Node head;
	private transient volatile Node tail;
	
	public class ConditionObject implements Condition {
    
    
        private transient Node firstWaiter;
        private transient Node lastWaiter;
	}
}

newCondition()

  • 就是ReentrantLock的一个获取Condition的方法,通过Sync,不能够被继承
public Condition newCondition() {
    
    
    return sync.newCondition();
}
final ConditionObject newCondition() {
    
    
            return new ConditionObject();
        }

await

  • 把当前线程存入node加入到队列
  • 释放线程所有的锁
  • 如果线程离开了同步队列,那么线程就要进入阻塞。
 public final void await() throws InterruptedException {
    
    
            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);
        }

addConditionWaiter

  • 首先判断队尾是不是condition状态,而且是不是空的,如果不是condition状态,那么就要清理队列并且重新设定好队尾
  • 如果是condition状态那么就创建节点(线程),并且放到队尾,如果队尾是空的,那么就让firstWaiter指向新节点,否则就是加入到队尾,并且设置新节点是队尾
  • 单链表只需要维持后继即可
  • 中断和signal都会把condition从-2到0
private Node addConditionWaiter() {
    
    
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    //判断队尾是不是condition,不是就清理,设置队尾
    if (t != null && t.waitStatus != Node.CONDITION) {
    
    
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //创建节点,并且存入线程,加入队尾或者是队尾是空那么就firstWaiter指向第一个节点
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

unlinkCancelledWaiters

  • 其实就是简单的单链表删除操作,遍历循环链表trail相当于就是最近的condition节点,t就是在往下面去遍历找到不是condition的节点。但如果是condition节点,相当于就是t往前面移动一个,trail往前面移动一次。
  • trail相当于就是前驱节点,也就是队列里面符合condition条件的最后一个节点,t找到删除节点,那么是不是就trail.next=t.next就把它给删了,如果没有trail就无法删除节点。
  • 就是单链表删除操作。找到condition就删了。
private void unlinkCancelledWaiters() {
    
    
            Node t = firstWaiter;
            Node trail = null;
            while (t != null) {
    
    
                //记录t下一个节点
                Node next = t.nextWaiter;
                //如果发现不是condition的节点,目标删除节点
                if (t.waitStatus != Node.CONDITION) {
    
    
                    //先断开连接t和下一个节点的连接
                    t.nextWaiter = null;
                    //如果前驱节点是null,说明才刚开始遍历,那么队列只有两个节点,直接把firstWaiter赋值next,就相当于跳过了t这个节点,也就删除了
                    if (trail == null)
                        firstWaiter = next;
                    else
                        //如果不是null,那么前驱节点还是跳过t,nextWaiter指向下一个节点
               
                        trail.nextWaiter = next;
                    //如果next是空的那么trail(因为判断到这肯定是非空),那么最后lastWaiter指向trail就可以了。
                    if (next == null)
                        lastWaiter = trail;
                }
                else//
                    trail = t;
                t = next;
            }
        }

fullyRelease

  • 其实就是通过release来释放所有的锁。先获取当前线程占据多少锁的状态。然后release
  • 如果有人没有锁的时候调用await,那么就会抛出异常而且把节点设置为cancelled(对应上面的删除节点)
 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;
        }
    }

isOnSyncQueue

  • 第一个判断就是如果不是condition而且有前驱的话那么就是可能在sync队列里
  • 如果next不是null那么只要在sync队里面可能有next
  • 如果node.next是空,那么还需要从尾到前确定node是不是就是在sync队伍里面,还是说还存在condition队里
final boolean isOnSyncQueue(Node node) {
    
    
    //如果不是condition和有前驱节点,那么可能入到sync里面了
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
   //如果next不为空,那么只有入sync的情况才有可能
        if (node.next != null) // If has successor, it must be on queue
            return true;
       //从尾到前遍历找到node,可能是队尾,也可能是enq的分叉问题
        return findNodeFromTail(node);
    }
 private boolean findNodeFromTail(Node node) {
    
    
     //从尾到前遍历找到node,可能是队尾,也可能是enq的分叉问题
        Node t = tail;
        for (;;) {
    
    
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }

signalAll释放Condition的队列

  • 首先就是获取单链表头
  • 然后调用doSignalAll来唤醒整个队列的线程。
 public final void signalAll() {
    
    
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
        }

doSignalAll

  • 这里其实就是把单链表的头结点和尾节点设置为空,然后使用传入过来的头结点,相当于就是不让其他线程访问到这个单链表。
  • 下面就是进行转移,把节点转移到Sync队列
  • 把节点放入Sync队尾,并且获取前驱节点。
  • 下面!compareAndSetWaitStatus(p, ws, Node.SIGNAL)保证前驱节点的状态是signal如果是false,那么就要去唤醒线程,执行shouldParkAfterFailedAcquiree,间接保证前驱节点是signal。最后返回成功
private void doSignalAll(Node first) {
    
    
            lastWaiter = firstWaiter = null;
            do {
    
    
                //转移链表所有的节点
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
        }

transferForSignal

方便理解下面的场景,什么时候signal来了会唤醒,什么时候不会唤醒

if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))条件解释?

  • transferForSignal实际上做的就是转移node,前提node是condition队列的

  • ws<=0而且设置compareAndSetWaitStatus(p, ws, Node.SIGNAL))失败说明前驱节点并不是signal,那么就通过LockSupport.unpark(node.thread);唤醒队列进入到acquireQueue来间接设置前驱节点状态保证是signal。

  • 如果一开始就设置signal成功就不会唤醒队列等待其它线程unlock来唤醒当前node

final boolean transferForSignal(Node node) {
    
    
        //把节点状态改成0,如果中断也是把节点状态改为0
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

        //把节点放入队尾并且获取前驱节点
        Node p = enq(node);
    //如果前驱节点是大于0说明哈没有被提示要唤醒后面的节点。
        int ws = p.waitStatus;
    //尝试去修改前驱节点的状态
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

signal

  • 实际上就是执行doSignal,传入first参数。
  • 然后doSignal处理方法就是让firstWaiter往后走一个,然后first脱链,接着就是尝试转移first。被转移的first必须是condition,其它跟上面一样。因为都是调用transferForSignal
 public final void signal() {
    
    
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
private void doSignal(Node first) {
    
    
            do {
    
    
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

假设前面的signal都没有被中断,那么什么时候会执行完await?

  • 当signal没有唤醒node线程的时候。
  1. 那么node在数据结构上在Sync上面
  2. node的线程仍然阻塞在await的park上
  3. 当另一个执行unlock的线程释放这个独享锁,那么才会去unpark来唤醒node线程
  • 当signal唤醒了node线程
  1. 数据结构在Sync上
  2. 线程已经尝试恢复执行但是阻塞在了Sync的shouldParkAfterFailedAcquire

结果还是需要unlock的线程来释放锁才能够await完全执行完成

signal前中断线程的过程?

  • THROW_IE会抛出异常、REINTERRUPT不会抛出异常,这两种都是被中断的状态而且会直接结束循环。
  • 中断线程调用checkInterruptWhileWaiting查看是不是被中断了,如果是那么就尝试调用transferAfterCancelledWait(node)来锁定状态修改并且把节点加入到队列。

为什么这里CAS可能会失败?

  • 因为signal和中断在竞争,谁到谁把线程放到Sync队列防止重复。
  • 这里的while (!isOnSyncQueue(node))
    Thread.yield();停顿的原因就是为了让节点enq完全送入Sync的时候才执行acquireQueue(其实就是await线程唤醒后重新进入到Sync阻塞的方法)

当前情景下只是进行了中断而且没有进行signal

  • 那么就是中断之后结束循环重新进入到Sync队列上
  1. node仍然在Condition,因为first.nextWaiter没有打断,但是也在Sync队列上
  2. 线程开始进入到Sync的队列进行阻塞。

中断和signal的区分

  • 中断其实是中断线程A中断当前线程B,也就是node线程。
  • signal其实就是signal线程想要唤醒线程B,但是signal线程会遍历condition链表找到线程B节点,并且会在转移节点transferForSignal中执行compareAndSetWaitStatus(node, Node.CONDITION, 0),而中断之后就是node线程B在运行执行到checkInterruptWhileWaiting的transferAfterCancelledWait也会进行compareAndSetWaitStatus(node, Node.CONDITION, 0)来进行竞争,主要就是想把节点搬到Sync然后执行acqurieQueue,让线程B去到Sync队列中阻塞。
private int checkInterruptWhileWaiting(Node node) {
    
    
            return Thread.interrupted() ?
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }
final boolean transferAfterCancelledWait(Node node) {
    
    
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
    
    
            enq(node);
            return true;
        }
        
         
            //为了让signal的enq把节点送到Sync才进行acquireQueue继续操作
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

关于中断没有那node移除出condition队列怎么办?

  • 其实是acquireQueued(node, savedState)之后(线程获取之前释放的所有锁),再次判断interruptMode是什么类型
  • 如果是0那么就是没有中断升级中断类型
  • 如果是REINTERRUPT那么就设置回REINTERRUPT没啥变化
  • 如果是THROW_IE那么没必要去降级了
  • 接着假设发生中断那么就会执行unlinkCancelledWaiters();删除之前设置不是condition的节点
  • 最后执行的 reportInterruptAfterWait(interruptMode);就是根据中断类型进行处理
  • 第一次中断比acquireQueue的中断更为重要
 public final void await() throws InterruptedException {
    
    
            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;
            }
     //重新进入Sync队列
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
  private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
    
    
      //抛出异常
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
      //重新对中断进行标记。
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }

await总结

await的持有锁过程

  • await进来的时候有锁
  • 执行fullyRelease之后无锁
  • acquireQueue之后又有锁
  • signal和signalAll必须要有锁才能唤醒await的线程

整个过程总结

  • await进来先释放所有锁
  • 然后封装成节点进入condition队列
  • 进入阻塞
  • 中断/signal竞争,线程节点转移回Sync队列,并且唤醒线程执行acquire,如果又中断那么就设置好中断级别
  • 最后就是如果有中断就删除之前中断的节点,并且执行对应中断级别的处理。await结束

awaitUninterruptibly

  • 其实和await不同的地方就是不需要对中断做太多处理,而且不允许中断立刻进入acquireQueue
  • 函数开头也不需要删除中断
  • 只需要记录中断或者没中断的状态
  • 在await中断或者是在acquireQueued(node, savedState)的时候中断都自我中断一次
  • 只有signal才能唤醒线程
 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;
            }
     //在await中断或者是在acquireQueued(node, savedState)的时候中断都自我中断一次
            if (acquireQueued(node, savedState) || interrupted)
                //再次设置中断,告诉线程被中断过
                selfInterrupt();
        }
 static void selfInterrupt() {
    
    
        Thread.currentThread().interrupt();
    }

awaitNacos

  • 其实就是做了一个定时任务,如果不能在限定时间阻塞获取锁,都会返回剩余等待时间。可以在超时之后直接执行acquireQueue
  • 如果有signal来过,而且超时,那么还是执行acquireQueue,如果signal没有来过那么就还在while循环里面等待nanosTimeout剩余变成负数,那么就会结束循环进入acquireQueue
  • 当超时的时候,signal来过才会转移node到Sync队列那么!isOnSyncQueue(node)失败,但是如果signal没有来过说明节点还没有进行转移那么!isOnSyncQueue(node)成功,而且超时那么会执行 transferAfterCancelledWait(node);来转移节点到Sync队列

出队的条件

  • node进入Sync,可能是signal线程做的,也可能是超时的时候做的。
  • 还有一种就是因为超时,转移节点之后推出循环
 public final long awaitNanos(long nanosTimeout)
                throws InterruptedException {
    
    
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
     //最终超时时间
            final long deadline = System.nanoTime() + nanosTimeout;
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
    
    
                //如果已经超时那么就转移节点到Sync队列不再进行等待
                if (nanosTimeout <= 0L) {
    
    
                    transferAfterCancelledWait(node);
                    break;
                }
                //如果剩余等待时间很少,那么就不进行阻塞,让它在while自旋
                if (nanosTimeout >= spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
                nanosTimeout = deadline - System.nanoTime();
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null)
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
    //返回剩余时间还有多少
            return deadline - System.nanoTime();
        }

await(long time, TimeUnit unit)

  • 多设置了时间单位,而且boolean类型的timeout来记录是不是超时问题。

await和Nacos不同的地方?

一个场景,signal来过导致while循环结束,但是由于unlock线程执行太久,导致await很久才执行。

  • 对于await来说,返回的只是原因,它并没有超时,而是signal唤醒(反应原因)
  • 对于awaitNacos来说最后的return deadline - System.nanoTime();处理出现的问题就是返回的是负数,因为unlock等待时间很长,那么导致他们两个的答案是不一样的。(反应超时时间,包括在acquireQueue的超时)
public final boolean await(long time, TimeUnit unit)
                throws InterruptedException {
    
    
            long nanosTimeout = unit.toNanos(time);
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            final long deadline = System.nanoTime() + nanosTimeout;
            boolean timedout = false;
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
    
    
                if (nanosTimeout <= 0L) {
    
    
                    timedout = transferAfterCancelledWait(node);
                    break;
                }
                if (nanosTimeout >= spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
                nanosTimeout = deadline - System.nanoTime();
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null)
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
            return !timedout;
        }

awaitUnit

 public final boolean awaitUntil(Date deadline)
                throws InterruptedException {
    
    
            long abstime = deadline.getTime();  //不同处1,直接获取过期时间
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            //   不同处2
            boolean timedout = false;
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
    
    
                if (System.currentTimeMillis() > abstime) {
    
      //不同处3,大于过期时间就是超时
                    timedout = transferAfterCancelledWait(node);
                    break;
                }
                LockSupport.parkUntil(this, abstime); 
                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);
            return !timedout;
        }

image-20211021234008944

image-20211021233901384

猜你喜欢

转载自blog.csdn.net/m0_46388866/article/details/120897583