AQS 之 Condition-的源码分析

在使用 Lock 锁的过程中,我们往往会使用到另外一个对象 Condition ,用于等待/通知模式的处理。

Condition 的创建

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

使用 Condition 的前提是获取锁

final ConditionObject newCondition() {
    return new ConditionObject();
}

从 newCondition 方法看出 Condition 对象实际上是 AQS 的内部类 ConditionObject ()。

成员变量

/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;

从内部定义的变量 firstWaiter, lastWaiter 看出, ConditionObject 对象内部维护了一个同样以 Node 为节点的等待队列。

await()

await 操作会使当前线程释放锁并进入等待模式。

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        // 当前线程中断 抛出中断异常
        throw new InterruptedException();
    // 将当前线程构造节点插入等待队列尾部
    Node node = addConditionWaiter();
    // 当前线程释放锁,唤醒同步队列 head 的后置节点
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    // 节点添加到同步队列后 退出循环
    while (!isOnSyncQueue(node)) {
        LockSupport.park(this);
        // 应该是在其他线程释放锁后被唤醒
        // 检查当前线程是否中断,若未中断则返回 0
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    // node 进入自旋过程尝试获取锁
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        // 移除等待队列中状态非 CONDITION 的节点
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 将当前线程构造节点并设置状态为 CONDITION
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        // 等待队列为空的时候将 firstWaiter 指向 node
        firstWaiter = node;
    else
        // 等待队列非空时将 lastWaiter 尾节点的 nextWaiter 指向 node
        t.nextWaiter = node;
    // 移动尾节点
    lastWaiter = node;
    return node;
}
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        int savedState = getState();
        // 当前线程释放锁,并唤醒同步队列中 head 的后置节点
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}
// 判断节点是否在同步队列上
final boolean isOnSyncQueue(Node node) {
    // 节点状态为 CONDITION 或 节点的前置为空 说明节点还在等待队列上
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 如果节点存在后置节点 next 则说明节点在同步队列上
    if (node.next != null) // If has successor, it must be on queue
        return true;
    /*
     * node.prev can be non-null, but not yet on queue because
     * the CAS to place it on queue can fail. So we have to
     * traverse from tail to make sure it actually made it.  It
     * will always be near the tail in calls to this method, and
     * unless the CAS failed (which is unlikely), it will be
     * there, so we hardly ever traverse much.
     */
    // 从 tail 尾节点开始遍历同步队列查找 node 节点;若存在返回 true,反之返回 false
    return findNodeFromTail(node);
}

await 操作流程如下 :

  • 将当前线程构造一个新的 node 节点,状态为 CONDITION 添加到等待队列尾部
  • 释放锁,唤醒同步队列 head 的后置节点
  • 判断当前 node 节点是否在同步队列中,若不在同步队列上则挂起当前线程,等待其他线程释放锁时被唤醒
  • 节点 node 被唤醒后若在同步队列上,则进入自旋过程再次尝试获取锁

signal()

signal 操作激活等待队列中节点

public final void signal() {
    // 判断当前线程是否为锁的持有者
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
private void doSignal(Node first) {
    do {
        // 判断 first 的后置节点是否为空,为空说明等待队列为空
        if ( (firstWaiter = first.nextWaiter) == null)
            // 等待队列的尾节点置为空
            lastWaiter = null;
        // 将 first 的后置节点置为空,也即是将 first 节点从等待队列中移除
        first.nextWaiter = null;

        // 执行信号转移
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
// 将节点从等待队列 (condition queue) 转移到 同步队列 (sync queue)
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    // 将节点状态设置为 0
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    // 将节点添加到同步队列(sync queue)尾部, 此时 p 应该是 node 的前置节点 ws 为 0
    Node p = enq(node);
    // 
    int ws = p.waitStatus;
    // 将 node 的前置节点状态改为 SIGNAL; 便于节点 p 释放锁的时候唤醒 node
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
    }

signal 操作的流程如下:

  • 将等待队列中的节点从队列中移除
  • 将等待队列中的节点状态由 CONDITION 改为 0
  • 将等待队列中的节点添加到 AQS 的同步队列尾部

signal 的作用 只是将节点从等待队列转移到同步队列中,只有当前线程释放锁后,转移到同步队列的节点才会有机会获取到锁。

如下图所示为 Condition 操作节点的转移过程:

image

小结

从 Condition 的 await()、signal() 操作可以看出,其作用等效于 Object 对象的 await(), notify() 方法;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 205,236评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,867评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,715评论 0 340
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,899评论 1 278
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,895评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,733评论 1 283
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,085评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,722评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,025评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,696评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,816评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,447评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,057评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,009评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,254评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,204评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,561评论 2 343

推荐阅读更多精彩内容