从ReentrantLock分析AbstractQueuedSynchronizer(AQS)—Condition分析

233 阅读11分钟

属性分析

在Condition里面有一个Condition Queue,这个Condition Queue是一个单链表,是通过Node得nextWaiter来关联起来得。下面得就是头和尾节点,Node是和WaitQueue总得节点类型是一致得。

private transient Node firstWaiter;

private transient Node lastWaiter;
    
    // 下面两个是中断模式。
    /** Mode meaning to reinterrupt on exit from wait */
    private static final int REINTERRUPT =  1;
    /** Mode meaning to throw InterruptedException on exit from wait */
    private static final int THROW_IE    = -1;

构造方法分析

没有什么可说得,一个无参构造。

   public ConditionObject() { }

常用方法分析

signal

做唤醒操作

唤醒一个,将Condition queue里面的头节点转移到WaitQueue中

public final void signal() {
    // 判断当前持有错的线程是否是当前线程
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
   // 拿到condition queue中的firstWaiter
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}	

doSignal

从Condition queue中转移,从头结点开始,将一个节点转移到Wait Queue中,如果在这个期间,当前节点的状态发生了变化,导致节点的类型不是CONDITION,Condition queue中指针后移。

private void doSignal(Node first) {
    do {
         // 头结点指针后移,如果头结点变为null了,那就说明队列已经空了,所以lastWaiter也是null
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
      
        // 清楚引用关系
        first.nextWaiter = null;
      
           // 将first从condition queue中转移到waitQueue中去。
          // 如果没有移动成功,将头结点赋值first,要注意,这个时候firstwaiter已经后移了。
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}	

AQS#transferForSignal

将node从condition queue转移到WaitQueue中

先修改Node的waitStatus,将waitStatus从CONDITION变为0,因为插入到WaitQueue中的节点的初始状态肯定是0。

在通过AQS的enq方法,将节点添加到 waitQueue中,enq方法返回的是当前节点插入到waitQueue中的前驱节点。

后面就会改变前驱节点的waitStatus。如果前继状态为CANCELLED,或者改变前驱节点的时候失败,直接唤醒当前节点这的线程。

final boolean transferForSignal(Node node) {
    // 改变状态,将CONDITION变为0,如果之前不是CONDITION,直接返回false。 
     // 一般来说,调用这个方法得node都是CONDITION,将CONDITION变为0,是为了之后得入waitQueue得操作,
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;


   
    //入队,enq方法会将这个node入队到WaitQueue中得。
    // 这个方法会返回当前插入节点得前驱节点。
    // 在waitQueue没有初始化得时候,这里得node就是header节点。会将Condition queue放在waitQueue中,因为放在waitQueue
    // 中得节点,初始得WaitStatus是0,所以,才有上面修改为0得操作。
    Node p = enq(node);
    //拿到头节点得waitStatus
    int ws = p.waitStatus;
    // ws > 0  : 节点得状态是取消
    //  !compareAndSetWaitStatus(p, ws, Node.SIGNAL) :将头节点得状态变为SIGNAL,
     // 如果前驱节点是取消状态,或者将前驱节点得state变为 Node.SIGNAL,失败,就唤醒当前节点得线程,
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
         // 进入到这里,说明直接唤醒节点中park得线程,
        // 这个操作说明,头节点已经有唤醒后继节点得任务,或者说,在cas得时候有别的线程已经设置了状态,导致firstWaiter 已经状态已经变了
        LockSupport.unpark(node.thread);
    return true;
}

signalAll

唤醒所有

将Condition Queue中得所有节点移动到 WaitQueue中

   public final void signalAll() {
        if (!isHeldExclusively())
            throw new IllegalMonitorStateException();
        Node first = firstWaiter;
        if (first != null)
            doSignalAll(first);
    }

doSignalAll

唤醒所有得节点

将Condition queue中所有的节点转移到 WaitQueue中。

transferForSignal方法在上面已经分析过了


private void doSignalAll(Node first) {
        // 因为要做唤醒所有得操作,所以,先将链表得头尾节点变为null
        lastWaiter = firstWaiter = null;
       // 然后开始遍历循环。一般来说,first都是头节点
        do {
             // 拿到下一个节点
            Node next = first.nextWaiter;
            // 清楚引用关系
            first.nextWaiter = null;
            // 
            transferForSignal(first);
            first = next;
        } while (first != null);
    }

await

等待

实现等待的操作大体有下面的几个步骤

  1. 构建Condition Queue。
  2. 释放锁。
  3. park线程,等待唤醒。
  4. 唤醒之后,如果有中断就处理中断,否则一切正常。

这是主要得一个方法,这里得代码逻辑要和signal结合在一起看,比较好理解,为了方便理解,画一个图看看。

image-20211102222515329.png

image-20211102222531180.png

image-20211102222541607.png

上面是下面代码的图像化,接下来继续说说while循环是怎么和signal结合起来的。

await和signal是怎么结合的?

  1. 首先要知道,有两个队列,一个是waitQueue(用作锁),一个是Condition Queue(用作Condition),不同的Condition是不同的Condition Queue。

  2. await操作会构建节点,添加到Condition Queue中,在没有signal之前,节点是不会到waitQueue中的。所以,while循环里面的isOnSyncQueue在signal之前,他的状态是CONDITION,并且它也没有在Wait Queue中,所以他一直返回的false。

  3. 在signal的时候,不管是signal还是signalAll(这俩只不过是一次和多次的差别),都会将node从Condition Queue中解绑,并且添加到Wait Queue中去。并且还会将当前节点的WaitStatus变为0,将当前节点在WaitQueue中的前驱节点的状态变为-1。(不包含取消节点)。

  4. 在上面的操作之后,当前节点已经到 WaitQueue中了,就走正常的锁的一套了,如果前驱节点释放锁之后,唤醒了当前节点,当前节点就会在while里面醒来,当然这里还少了一步判断park期间有没有中断。while条件不满足,就继续走下面的代码,(获取锁,判断中断模式)

    要知道 park是可以响应中断的,并且中断是不会报错的,只是改了中断标志。

  public final void await() throws InterruptedException {
       // 判断当前线程是否中断,如果中断,直接抛出异常
        if (Thread.interrupted())
            throw new InterruptedException();
       // 构建ConditionQueue节点
        Node node = addConditionWaiter();
       // 释放当前持有得锁 
        int savedState = fullyRelease(node);
      
       // 中断模式默认
       // 这个用于判断中断,不同得中断有不同得模式
        int interruptMode = 0;
       // 通过这个条件判断,是有signal操作,
         // 如果唤醒,这里得判断就不会进去了,
        // 因为当前得节点已经在waitQueue中了,
     // 这里的这个要和signal结合起来,signalAll会将Condition Queue里面的节点全部移动到WaitQueue中,并且会将
     // 节点的waitStatus先改为0,在把节点的前驱节点变为-1.
    // 所以,在没有调用ignal之前,当前节点肯定是没有在waitQUeue中的。
        while (!isOnSyncQueue(node)) {
             // 如果没有,就park当前线程
            LockSupport.park(this);
             // 之前说过,pakr得线程是可以响应中断得,
            // 要是一切正常得话,就不会走到这里,上面得while循环会退出。
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
        }
      
       // 重新获取锁 并且interruptMode != THROW_IE
      // acquireQueued 返回值 true。 表示当前线程在获取锁得时候中断了。
      // 返回false,说明当前线程没有中断。
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            // 如果获取锁得时候再次中断了,并且interruptMode不是THROW_IE,重新赋值为REINTERRUPT。
            interruptMode = REINTERRUPT;
       // 如果在conditionQueue中,当前节点还有nextWaiter,
        if (node.nextWaiter != null) 
           // 如果没有
          unlinkCancelledWaiters();
        
       // 通过中断模式来做操作,
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
    }

addConditionWaiter

添加一个新得waiter到wait Queue中

  private Node addConditionWaiter() {
       // 尾节点
        Node t = lastWaiter;
        // 如果lastWaiter不是null并且lastWaiter的waitStatus不是CONDITION,
        if (t != null && t.waitStatus != Node.CONDITION) {
            unlinkCancelledWaiters();
            // 
            t = lastWaiter;
        }
       // 创建节点,状态是CONDITION
        Node node = new Node(Thread.currentThread(), Node.CONDITION);
        // 入队
        if (t == null)
            firstWaiter = node;
        else
            t.nextWaiter = node;
        lastWaiter = node;
        return node;
    }

unlinkCancelledWaiters

这个我不是很理解,不太懂这个是干嘛。为什么要在addWaiter和 node.nextWaiter != null,是什么样的情形下面会出现这样操作 todo 之后补充

private void unlinkCancelledWaiters() {
     // 头节点
    Node t = firstWaiter;
    
    Node trail = null;
    // 开始循环
    while (t != null) {
        
        Node next = t.nextWaiter;
        
         // t(在第一次循环得时候,就是头节点) 不是 condition得时候
        if (t.waitStatus != Node.CONDITION) {
            // nextwaiter变为null,消除引用关系
            t.nextWaiter = null;
            if (trail == null)
                // 修改头节点为t得next(第一次循环得时候就是头节点得下一个节点)
                firstWaiter = next;
            else
                // trail连接起来
                trail.nextWaiter = next;
            if (next == null)
                // 如果next为空,将trail复制给lastWaiter
                lastWaiter = trail;
        }
        else
             // trail为t(第一次为头节点)
            trail = t;
        // 指针后移
        t = next;
    }
}

AQS#fullyRelease

调用AQS中得release方法,将当前锁得state作为参数传递进去。并且返回state,否则就报错,并且这个节点得waitStatus变为CANCELLED。

final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
         // 这方法是release得,总之就是释放锁,如果释放成功了,就直接返回原来得state
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
         // 如果失败了,就会将当前节点得waitStatus变为Node.CANCELLED
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}

AQS#isOnSyncQueue

判断当前得节点是否在同步队列里面(WaitQueue中)

node的状态是CONDITION,或者node没有前驱(注意,这里说的前驱是在WaitQueue)。就说明没有在waitQUeue中。

如果节点有后继,说明他已经在waitQueue中了

如果说有前驱节点但是没有后继节点,说明它是尾节点,所以,下一步就开始从尾节点开始,遍历判断了

final boolean isOnSyncQueue(Node node) {
     // 如果waitStatus是CONDITION 或者没有前驱(说明肯定没有到waitQueue中)
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 如果有后继节点,他肯定是已经在队列里面了
    if (node.next != null) 
        return true;
    // 如果他不在waitQueue队列里面,或者在waitQueue得尾节点, waitStatus不是CONDITION就会走到这里,从后往前搜。
    return findNodeFromTail(node);
}

AQS#findNodeFromTail

从后往前搜索,如果节点在一个同步得queue中,只有isOnSyncQueue.方法才可以调用他。

  private boolean findNodeFromTail(Node node) {
       // 从后往前面搜
        Node t = tail;
        for (;;) {
             // 死循环
             // 找到了
            if (t == node)
                return true;
            // 找完了
            if (t == null)
                return false;
            // 指针前移
            t = t.prev;
        }
    }

checkInterruptWhileWaiting

检查当前得节点在park得时候,有没有被中断过。如果没有中断过,就返回0,否则就到transferAfterCancelledWait里面判断。

如果中断在signalled之前,返回THROW_IE,在signalled之后,返回REINTERRUPT。

private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

AQS#transferAfterCancelledWait

判断当前被中断得线程中断得时候是在唤醒 (signalAll或者signal) 操作之前还是之后

在signal之前得节点,他得waitStatus肯定是CONDITION,如果是CONDITION,就会将waitStatus变为0,重新入队。

在signal之后得节点,他得waitStatus肯定不是CONDITION,会在while循环里面继续判断当前得节点是否在waitQueue中,如果不存在,当前得线程让出cpu使用权。等待signal。等signal之后,再返回。这就是 中断在唤醒(signalAll或者signal)之后。

final boolean transferAfterCancelledWait(Node node) {
     // 再signal之前,node得状态肯定是CONDITION,如果不是condition,就说明在signal之后。
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            //重新入队
            enq(node);
            return true;
        }
       // while 判断当前得节点是否在队列中,如果没有在队列中,就循环让出cpu使用权,等待一下下
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

reportInterruptAfterWait

通过前面确认得中断模式来判断,按照中断模式来抛出异常或者调用当前线程得中断方法。

   
    private void reportInterruptAfterWait(int interruptMode)
        throws InterruptedException {
        if (interruptMode == THROW_IE)
            throw new InterruptedException();
        else if (interruptMode == REINTERRUPT)
            selfInterrupt();
    }

awaitNanos

带超时时间的await

基本的功能是和await一样,和ReentrantLock的tryLock(带超时时间)的实现是一样的,在await方法上面,添加了超时时间。

典型的超时时间的写法是,先用当前实现+超时时间=到期时间。然后再唤醒之后用到期时间-当前时间,如果小于0,说明已经超时了。

  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)) {
           // 如果小于等于0,说明awaitNanos不需要等待,直接将当前节点添加到waitQueue中,重新获取锁
            if (nanosTimeout <= 0L) {
                transferAfterCancelledWait(node);
                break;
            }
           // 还是spinForTimeoutThreshold,熟悉的感觉,
            if (nanosTimeout >= spinForTimeoutThreshold)
                LockSupport.parkNanos(this, nanosTimeout);
           // 检查中断
            if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                break;
            nanosTimeout = deadline - System.nanoTime();
        }
     // 这里就和await的一样了
        if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
            interruptMode = REINTERRUPT;
        if (node.nextWaiter != null)
            unlinkCancelledWaiters();
        if (interruptMode != 0)
            reportInterruptAfterWait(interruptMode);
    
     // 返回剩余时间
        return deadline - System.nanoTime();
    }

awaitUntil

await(long time, TimeUnit unit)

这几个超时时间的await,本体都和await的一样了。无非都是添加超时时间。

awaitUninterruptibly

不中断,不间断的await

本地还是await,不过没有中断模式的判断了。只要有中断发生(不管是在Condition queue还是wait Queue),都会调用当前线程的interrupt方法。

   public final void awaitUninterruptibly() {
        // 构建节点
        Node node = addConditionWaiter();
        // 释放锁,并且将原来得state返回
        int savedState = fullyRelease(node);
       // 
        boolean interrupted = false;
        
        while (!isOnSyncQueue(node)) {
            // 这里就直接唤醒了,肯定是唤醒之后,上面得条件不满足,然后重新获取锁
            LockSupport.park(this);
            if (Thread.interrupted())
                interrupted = true;
        }
        //  如果获取锁的时候发生了中断,获取当前线程在park的时候发生了中断,就调用自己的中断方法。
        if (acquireQueued(node, savedState) || interrupted)
            selfInterrupt();
    }

hasWaiters

判断condition上是否还有waiter

public boolean hasWaiters(Condition condition) {
    if (condition == null)
        throw new NullPointerException();
    if (!(condition instanceof AbstractQueuedSynchronizer.ConditionObject))
        throw new IllegalArgumentException("not owner");
   // 这是调用的同步器的,直接看
    return sync.hasWaiters((AbstractQueuedSynchronizer.ConditionObject)condition);
}

AQS#hasWaiters

 public final boolean hasWaiters(ConditionObject condition) {
        if (!owns(condition))
            throw new IllegalArgumentException("Not owner");
        return condition.hasWaiters();
    }

ConditionObject#hasWaiters

循环遍历 condition queue,判断节点的状态是否有一个是CONDITION

protected final boolean hasWaiters() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            for (Node w = firstWaiter; w != null; w = w.nextWaiter) {
                if (w.waitStatus == Node.CONDITION)
                    return true;
            }
            return false;
        }

getWaitQueueLength

判断当前的Condition里 Condition queue的数量,这里的代码逻辑很简答了,就不在这里列出来了,本质就是遍历Condition queue。

getWaitingThreads

判断当前的Condition里等待的线程的数量,这里的代码逻辑很简答了,就不在这里列出来了,本质就是遍历Condition queue,判断CONDITION。

关于博客这件事,我是把它当做我的笔记,里面有很多的内容反映了我思考的过程,因为思维有限,不免有些内容有出入,如果有问题,欢迎指出。一同探讨。谢谢