Condition源码分析

参考文献:http://www.jianshu.com/p/6b5aa7b7684chttp://ifeve.com/understand-condition/http://www.cnblogs.com/cm4j/p/juc_condition.html


Condition的使用

通常我们会这样来new一个Condition

ReentrantLock lock = new ReentrantLock(); //默认的构造方法创建的是非公平锁
Condition isEmpty = lock.newCondition();
Condition isFull = lock.newCondition();

condition中的方法很像普通对象锁的wait()和notify(),而一个lock理论上可以创建n个condition,这也是JUC包带来的一大改变


Condition结构

当我们调用ReentrantLock里的condition方法时,他返回的是一个ConditionObject对象

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

该对象是AQS类的一个内部类,以下是部分类对象,可见Condition内部也维护了一个队列

    public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        /** First node of condition queue. */
        private transient Node firstWaiter;
        /** Last node of condition queue. */
        private transient Node lastWaiter;

await方法

调用await,当前线程在此Condition上等待,同时将此线程实例化成一个Node,加入等待队列中

        public final void await() throws InterruptedException {
            //判断是否中断
            if (Thread.interrupted())
                throw new InterruptedException();
            //在该Condition维护的等待队列中增加节点    
            Node node = addConditionWaiter();
            //将当前的线程占用的Lock释放,通过修改Lock的State实现
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            //释放完毕后,遍历AQS的队列,看当前节点是否在队列中,不在,说明它还没有竞争锁的资格,所以继续将自己沉睡。
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            //这个时候线程已经被signal()或者signalAll()操作给唤醒了,退出了上面的while循环
           // 自旋等待尝试再次获取锁,调用acquireQueued方法
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

首先是检查中断。如果中断,那么抛出中断异常。
如果没有中断,调用addConditionWaiter():

在Condition的队列(是一个双向链表)中增加一个Node节点,代表当前Wait的线程,等待Signal信号唤醒。
此处Node的首尾节点都没有用Volatile修饰,是因为到await方法的线程都是获取锁的,利用锁已经能保证了Node的可见性了。也就是说,此方法本身就是线程安全的,这里就没必要利用Volatile保证可见性。

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

接下来是判断当前的节点是不是在同步队列中,这里可能会有一点疑问,就是为什么是判断同步队列而不是等待队列呢?
其实在后面signal的时候会有一个将node转入到同步队列的过程,从而使线程重新加入到锁竞争中。这里判断是否在同步队列中,其实就是判断是不是已经signal:

    final boolean isOnSyncQueue(Node node) {
        //此处第一个判断当前Node的状态,如果是CONDITION,表明是正常的没有被唤醒的节点,返回false 表名当前的节点还没进入到Syn队列
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        // 说明当前Node的状态不是CONDITION,同时它既有prev节点也有next节点,那么说明它在AQS队列里    
        if (node.next != null) 
            return true;
        //遍历整个NodeSyn队列,如果存在返回true,这里有一个Wait队列到Syn队列的转化过程    
        return findNodeFromTail(node);
    }

如果之前返回了true,那么表示已经在队列中,那么继续争抢锁,这种情况主要发生在已经释放锁,但是还没有park线程的时候,线程就被singal。如果返回false,证明没有singal,所以要park当前的线程。


signal方法
        public final void signal() {
            //检查当前线程是不是Lock占用的线程
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            //取出Condition队列中第一个Node
            if (first != null)
                //唤醒
                doSignal(first);
        }
        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)//修改头结点,完成旧头结点的移出工作
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) && //将老的头结点,加入到AQS的等待队列中
                     (first = firstWaiter) != null);
        }
    final boolean transferForSignal(Node node) {
        /*
         * 如果无法改变状态,说明该节点已经被取消了;
         * 如果cancelled,返回false,上层的while循环会继续唤醒下一个节点
         */
        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).
         */
        Node p = enq(node); // 加入AQS队列
        int ws = p.waitStatus;
        //如果该结点的状态为cancel或者修改waitStatus失败,则直接唤醒。
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

可以看到,正常情况 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL) 这个判断是不会为true的,所以,不会在这个时候唤醒该线程。

上面的代码很容易看出来,signal就是唤醒Condition队列中的第一个非CANCELLED节点线程。

signalAll和signal方法类似,主要的不同在于它不是调用doSignal方法,而是调用doSignalAll方法:

private void doSignalAll(Node first) {
    lastWaiter = firstWaiter  = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}

这个方法就相当于把Condition队列中的所有Node全部取出插入到等待队列中去。


总结

AQS自己维护的队列是当前等待资源的队列,AQS会在资源被释放后,依次唤醒队列中从前到后的所有节点,使他们对应的线程恢复执行。直到队列为空。

而Condition自己也维护了一个队列,该队列的作用是维护一个等待signal信号的队列,两个队列的作用是不同,事实上,每个线程也仅仅会同时存在以上两个队列中的一个,流程是这样的:

1.线程1调用reentrantLock.lock时,线程被加入到AQS的等待队列中。

2.线程1调用await方法被调用时,该线程从AQS中移除,对应操作是锁的释放。

3.接着马上被加入到Condition的等待队列中,意味着该线程需要signal信号。

4.线程2,因为线程1释放锁的关系,被唤醒,并判断可以获取锁,于是线程2获取锁

5.线程2调用signal方法,这个时候Condition的等待队列中只有线程1一个节点,于是它被取出来,并被加入到AQS的等待队列中。 注意,这个时候,线程1 并没有被唤醒。

6.signal方法执行完毕,线程2调用reentrantLock.unLock()方法,释放锁。这个时候因为AQS中只有线程1,于是,AQS释放锁后按从头到尾的顺序唤醒线程时,线程1被唤醒,于是线程1恢复执行。

7.直到释放所整个过程执行完毕。

可以看到,整个协作过程是靠结点在AQS的等待队列和Condition的等待队列中来回移动实现的,Condition作为一个条件类,很好的自己维护了一个等待信号的队列,并在适时的时候将结点加入到AQS的等待队列中来实现的唤醒操作。


注意

上面的将线程一从AQS中移除并加入到Condition队列中,移除对应的操作是AQS中head的重置。
我们知道只有获得锁的线程才能调用await或signal方法,所以当调用await方法时,意味着该线程所对应的Node是AQS的头结点,当它放弃锁时,唤醒了它的后继节点,后继节点得锁后(在acquireQueued方法中)设置自身为新的head,从而线程一也相应地在AQS中被移除。

http://www.cnblogs.com/cm4j/p/juc_condition.html 里面的图不错

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值