1.5 详解AQS-ConditionObject

ConditionObject同样有一个队列,且保持队列的头结点和尾结点:

  • Node firstWaiter
  • Node lastWaiter

ConditionObject对外暴露signalawait方法用于唤醒和等待。对于竞争资源的线程来讲,需要先等待再唤醒。无论是signal还是和await,都需要当前线程持有锁才能操作。

对于await来讲,应该是当前线程主动阻塞,然后将当前线程加入到等待队列中,等待其他线程唤醒。而唤醒signal应该是将这些主动阻塞的线程唤醒,给他们竞争锁的机会。

那么先看await
源码:

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);
}

大致逻辑:

  1. 判断线程是否中断,如果中断抛出异常
  2. 将线程入condition队列,释放当前线程持有的锁,并获取当前同步器状态
  3. 判断节点是否在同步队列中,如果不在,阻塞当前线程。直到被唤醒或者中断
  4. 使用保存的同步器状态重新获取锁
  5. 如果在3中发生中断,那么抛出中断异常
addConditionWaiter
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)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

addConditionWaiter将一个节点添加到condition队列中。在入队时,判断当前尾节点是不是CONDITION。如果不是则判断当前尾节点已经被取消,将当前节点出队。那么也就是说在队列中的节点状态,要么是CONDITION,要么是CANCELLED。具体的节点状态变化逻辑后面再看。

unlinkCancelledWaiters
private void unlinkCancelledWaiters() {
    Node t = firstWaiter;
    Node trail = null;
    while (t != null) {
        Node next = t.nextWaiter;
        if (t.waitStatus != Node.CONDITION) {
            t.nextWaiter = null;
            if (trail == null)
                firstWaiter = next;
            else
                trail.nextWaiter = next;
            if (next == null)
                lastWaiter = trail;
        }
        else
            trail = t;
        t = next;
    }
}

方法的作用是移除取消的节点。方法本身只有在持有锁的时候会被调用。方法会遍历当前condition队列,将所有非Condition状态的节点移除。思考:为什么要遍历所有节点 且 取消所有的节点呢?有节点取消的时候,从队列中移除这个节点不就可以了?为什么要从头到尾遍历所有的节点,保持队列的整洁呢?

因为是链表队列,所以如果取消指定节点,需要从头遍历到指定节点,然后才能取消。在节点取消发生次数加多的情况下,会重复遍历队列。而如果将所有无效节点取消,那么节点取消时,只改变自身节点的状态即可。

fullyRelease

在节点添加到Condition队列后,获取当前Sync队列的状态。此处的作用是尝试释放锁,如果释放成功则返回节点状态;如果失败,那么当前节点取消。(节点如果await,那么其持有的锁应该释放,让其他线程去持有锁)

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

在释放锁之后,随即是一个循环,判断当前节点是否在队列中,如果不在队列中,则挂起当前线程。

final boolean isOnSyncQueue(Node node) {
    /* ConditionObject中的节点并没有定义前置节点和后继节点,且节点的有效状态为CONDITION。而Sync中的节点,有前置节点和后继节点,且必有前继节点(dunmy 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;
    return findNodeFromTail(node);
}

总共是三个条件来判断是否在队列中:

  1. 节点状态是否是CONDITION 或者节点是否有前继节点
  2. 在不满足的时候,节点的后继节点不为空
  3. 在不满足1和2的时候,从尾部向前遍历可以查到当前节点。

条件1只是说,如果存在状态为CONDITION或者有前继节点,那么这个节点肯定不在队列中。具体原因看注释

条件2说 节点的状态不是CONDITION 并且 节点有前继节点 并且 节点有后继节点,那么节点一定在同步队列中。为什么一定要有前继和后继才算是在同步队列中?少一个行不行?
可以只有后继节点。因为AQS中的节点如果有next,那么一定在队列中。但是反之,如果在队列中, 不一定有next。比如tail节点。而如果只有前继节点,并不能保证是在同步队列中。具体原因可以看cancelAcquire方法。方法中并没有完全解除取消节点的prev

findNodeFromTail

findNodeFromTail 比较简单,不做过多理解

private boolean findNodeFromTail(Node node) {
    Node t = tail;
    //从尾部遍历clh队列
    for (;;) {
        //找到了当前节点
        if (t == node)
            return true;
        //没有知道当前节点
        if (t == null)
            return false;
        t = t.prev;
    }
}
checkInterruptWhileWaiting

线程已经被阻塞,那么如果线程被唤醒有两种情况:被signal唤醒 或者 线程中断了。所以在线程被唤醒之后,检查线程是否中断。
在循环过程中还会检查当前线程是否中断,如果中断了,取消循环。对于checkInterruptWhileWaiting则是检查是否在park的时候被中断唤醒,具体实现在transferAfterCancelledWait中,返回true表示中断在signal之前,那么需要抛出中断异常;如果返回false,说明中断在single之后,此时只需要响应中断即可。

ConditionObject为中断响应定义个两种模式:

  • REINTERRUPT 1 重新响应中断
  • THROW_IE -1 抛出中断异常
  • 0 没有中断

transferAfterCancelledWait做了什么:

final boolean transferAfterCancelledWait(Node node) {
    if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
        enq(node);
        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.
     */
    while (!isOnSyncQueue(node))
        Thread.yield();
    return false;
}
signal

源码:

public final void signal() {
   //如果当前线程持有锁且为独占模式
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    //获取头结点且唤醒头结点
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}

doSignal:

private void doSignal(Node first) {
    do {
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

简单逻辑说明:从ConditionObject的队列从头开始遍历,有效唤醒第一个节点

transferForSignal:

final boolean transferForSignal(Node node) {
    //源码注释:如果不能改变状态,说明节点已经取消
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    // 将当前节点加入到等待队列中,并尝试将前置节点的状态设置为`SIGNAL`,如果失败,直接唤醒当前进程
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值