AbstractQueuedSynchronizer之conditionLock

本文详细探讨了AbstractQueuedSynchronizer(AQS)的conditionLock,重点解析了await方法的执行流程,包括释放锁、进入阻塞状态、条件队列的管理等关键步骤。同时,提到了signal()方法的doSignal内部实现,涉及节点转移和唤醒信号的逻辑。
摘要由CSDN通过智能技术生成

条件锁,参考http://ifeve.com/aqs-2/中条件锁实现的基础思路。在条件锁中,绝大多数时候,方法是处于单线程执行的过程(因为在使用条件锁的时候,必须获取排它锁)。

conditionLock的关键方法await():

API中的描述:

implements interruptible condition wait.

  1. If current thread is interrupted, throw InterruptedException.
  2. Save lock state returned by getState.
  3. Invoke release with saved state as argument, throwing IllegalMonitorStateException if it fails.
  4. Block until signalled or interrupted.
  5. Reacquire by invoking specialized version of acquire with saved state as argument.
  6. If interrupted while blocked in step 4, throw InterruptedException.

第一步,如果线程有中断状态,则立刻抛出异常
第二步,保存state,这里的state实际上是获取排他锁的时候设置的状态,如果具体到实现ReentrantLock这里大多数应该是1

----------以上面完全是串行操作,是在拥有一个排它锁的状态下执行的---------

第三步,释放这个排它锁(首先检测当前线程是否获取到了排它锁,没获取到是会抛出异常的),让后驱节点的线程能够执行,进入到条件锁的await方法里面。

-------第三步操作会导致后驱节点的执行,由此,开始进入多线程操作的环境---------

第四步,进入block状态。这里是和条件锁的signal()/signalAll()有着密切关系,只有signal()/signalAll()调用,触发条件锁的Node转移到“CHL”队列的时候,这里的block状态才有机会解除(中断这种非正常情况不考虑了)。

第五步,当block状态解除的时候,这个时候需要检测“CHL”获取到一个排他锁,来执行后面的操作。(个人觉得如果只有signal()应该是完全不用再次尝试获取排他锁,因为signal方法只会触发某个节点的解除block状态;signalAll()则会触发所有的线程解除block状态,所以这个时候需要检测“CHL”队列来获取排它锁

-------第五步,又让所有的操作开始进入串行化-----------------------

第六步,如果在block状态的时候发生了中断,抛出异常

ConditionObject这个conditionLock实现类中,成员变量firstWaiter和lastWaiter中甚至没有使用volatile关键字。

一步一步说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.addConditionWaiter()方法调用,会创建一个新Node并且加入到conditionLock等待队列、剔除那些已经被取消的队列节点。

2.fullyRelease(node)会导致,“CHL”队列中的后驱节点上的线程被唤醒。(记住我们的前提,条件锁的使用是执行获取排它锁。也就是当前线程执行到这步,会有两个Node,一个在排它锁队列“CHL”,一个在条件锁的队列里面)。

3.isOnSyncQueue(node)这里,判断如果不在排它锁队列,则进入block,等待进入“CHL”队列机会(其他线程调用的signal/signalAll调用会给这个机会)

4.checkInterruptWhileWaiting(node),主要检查中断状态,为了实现 JSR133修订以后,就要求如果中断发生在signal操作之前,await方法必须在重新获取到锁后,抛出InterruptedException。但是,如果中断发生在signal后,await必须返回且不抛异常,同时设置线程的中断状态。

5.acquireQueued(node, savedState) 正常情况下,这里其实没多大用(因为该这个节点解除block,它正好会在头结点),主要是因为上一句红字部分,导致必须这样做,因为中断会导致block状态异常解除,这个时候又必须符合红字部分的情况(必须重新获取锁)。

。。。。后面几个方法作用,从名字看。

---------

关键方法signal()调用的doSignal:

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

这里为什么可以直接调用,还是那个大前提,因为这个方法的调用,也是在获取到排它锁的情况下执行的。

transferForSignal(Node node)让在条件锁队列的Node,进入排它锁队列“CHL”队列,并且设置前驱节点的状态为Node.SIGNAL表示需要唤醒信号。

这里如果Node转移失败了,会唤醒Node中的线程。根据代码, transferForSignal失败的原因不知道为什么,反正JavaDoc说是无害的。




  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值