ReentrantLock学习(五)Condition中await&signal

一、简介

ReentrantLock 中ConditionObject内部类,实现了Condition接口,内部维护了一个单向链表(firstWaiter、lastWaiter),而Node中的nextWaiter正是用于该单链表

await:释放当前锁持有的锁,生成线程等待node,存储到condition中的单链表中,等被唤醒的时候,在加入到锁的等待队列

signal:唤醒condition等待队列里的一个线程(firstWaiter)

signalAll: 循环唤醒condition等待队列里的所有线程 

condition中单链表

 

二、方法详解 

1、await方法详解

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //在condition队尾增加一个等待节点
    Node node = addConditionWaiter();
    //释放全部锁,并且唤醒同步队列中下一个等待线程
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //节点是否添加到了同步队列
    while (!isOnSyncQueue(node)) {
        //不在同步队列,挂起当前线程
        LockSupport.park(this);
        //interruptMode 
        //线程未中断,返回0
        //线程中断了,设置该节点状态-2 —> 0 成功,返回-1 THROW_IE
        //线程中断了,设置该节点状态-2 —> 0 失败,返回1 REINTERRUPT
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //acquireQueued做的事,申请锁(公平锁看队列有没有正在排队的,非公锁则直接抢锁),若没申请下,则park线程,等待unpark唤醒;若申请下,则返回boolean,true代表线程中断,false代表线程未中断
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null) // clean up if cancelled
        //清理node.nextWaiter引用
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        //interruptMode == REINTERRUPT,则设置线程中断标识
        //interruptMode == THROW_IE 抛出线程中断异常
        reportInterruptAfterWait(interruptMode);
}

await流程图

await流程图

再看方法checkInterruptWhileWaiting中的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;
}

其中 transferAfterCancelledWait方法中,将node的状态从CONDITION(-2)修改为0的这步,修改成功的话,则返回 true,即中断模式 THROW_IE(意味这接下来就要抛出 InterruptedException),这是因为,signal会将node的状态修改为0,如果这里修改成功,说明signal还没有修改状态,线程唤醒发生在signal之前,这种情况一般出现在await一定时间上,等待超时了。如果transferAfterCancelledWait方法修改node状态失败,说明signal已经修改了node的状态,从CONDITION(-2)修改为0,然后判断signal是否将节点添加到了同步队列里,没有则自旋等待,让出CUP时间。

2、signal&signalAll方法详解

public final void signal() {
    //当前持有锁的线程不是自己,则抛出异常(说明signal执行的前提是获取到锁)
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}
private void doSignal(Node first) {
    do {
        //将firstWaiter后移,指向下个节点,这里是将node从condition的等待队列移除
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    //transferForSignal修改node状态,并将node添加到同步队列,成功返回true,失败false,成功就终止循环(只唤醒一个),失败则处理下一个node
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
private void doSignalAll(Node first) {
    //清空condition队列(first备份了)
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
//transferForSignal修改node状态,并将node添加到同步队列,成功返回true,失败false,因为是全部唤醒,所以不关心返回结果
        transferForSignal(first);
        first = next;
        //循环处理等待队列的每个节点,直到处理完
    } while (first != null);
}

signal流程(此处省略signalAll,signalAll就是内部循环transferForSignal)

signal流程图

再看方法doSignal中的transferForSignal

final boolean transferForSignal(Node node) {
    //这里修改节点状态,与await中修改节点状态竞争,
    //如果这里修改失败了,说明线程已经唤醒,且在await中修改了节点状态
    //如果修改成功了,则await中的线程可能唤醒了可能没唤醒,唤醒的话就等待该方法将节点添加到同步队列,没有唤醒的话,就等待唤醒
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    //添加节点到同步队列
    Node p = enq(node);
    int ws = p.waitStatus;
    //前置节点状态大于0,则说明前置节点已经撤销,唤醒当前节点线程
    //修改前置节点状态为Node.SIGNAL,修改失败的话唤醒当前节点线程(其实此处不知道为啥会失败,官方注释说会出现状态异常,没什么影响)
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

三、await、signal、signalAll中同步队列、等待队列节点变化 

假设前提:thread1占有锁,thread2在同步队列等待,thread0在condition队列等待

1、同步器状态变化 

当thread1执行await,其中会调用releaseAll,将同步器状态修改为0,将exclusiveOwnerThread修改为null,然后调用unparkSuccessor,唤醒thread2,thread2申请下锁,将同步器状态修改为1,设置exclusiveOwnerThread为thread2

状态变化

2、同步队列、等待队列变化

调用await前

 

调用await后

 

调用signal后

 

调用signalAll后

 

 

 

 

 

 

 

 

  • 3
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值