Condition接口

任意一个java对象,都拥有一组监视器方法(定义在Object上),主要包括wait()、wait(long timeout)、notify()、notifyAll()方法,这些方法与Synchronized同步关键字配合使用,可以实现等待/通知模式。Condition接口也提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。二者的区别如下:

对比项Object Monitor MethodsCondition
前置条件获取对象的锁调用Lock.lock()获取锁,调用Lock.newCondition()获取Condition对象
调用方式直接调用,object.wait()直接调用,Condition.wait()
等待队列个数一个多个
当前线程释放锁并进入等待状态支持支持
当前线程释放锁并进入等待状态,在等待状态中不响应中断不支持支持
当前线程释放锁并进入超时等待支持支持
当前线程释放锁并进入指定时间的等待状态不支持支持
唤醒等待队列中的一个线程支持支持
唤醒等待队列中的全部线程支持支持

Condition的实现分析

ConditionObject是同步器AbstractQueuedSynchronizer的内部类。每个Condition对象都包含着一个等待队列,该队列是Condition对象实现等待/通知功能的关键。

1.等待队列

等待队列是一个FIFO的队列,在队列中的每个节点都包含了一个线程引用,该线程是在Condition对象上等待的线程,如果一个线程调用了Condition.await()方法,那么该线程将会是释放锁、构造节点加入等待队列并进入等待状态。同步队列和等待队列中节点类型都是同步器的静态内部类AbstractQueuedSynchronizer.Node

一个Condition包含一个等待队列,Condition拥有一个首节点(firstWaiter)和一个尾节点(lastWaiter)。当前线程调用Condition.await()方法,将会以当前线程构造节点,并将节点从尾部加入等待队列,等待队列的基本结构如图所示:

如图所示,Condition用于首节点和尾节点的引用,而新增节点只需要将原有的尾节点的nextWaiter指向它,并且更新尾节点即可。节点引用更新操作的过程并没有使用到CAS,因为调用await方法的线程必定是获取了锁的线程。

在Object的监视器上,一个对象拥有一个同步队列和一个等待队列,但是并发包中的Lock拥有一个同步队列和多个等待队列。

Condition的实现是同步器的内部类,因此每个Condition实例都能访问同步器提供的方法,相当于每个Condition都拥有所属同步器的引用。

2.等待

如果从队列的角度看await方法,当调用await方法时,相当于把同步队列的首节点移到Condition的等待队列中。

        public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();//当前线程加入到Condition的等待队列中
            int savedState = fullyRelease(node);//释放同步状态
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {//当前节点不在同步队列上等待重新获取同步状态
                LockSupport.park(this);//阻塞当前线程
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)//判断是否在阻塞等待时被中断,返回0表示没有
                    break;
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)//加入到获取同步状态的竞争中
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) 
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

调用该方法 的线程成功获取了锁,也就是同步队列中的首节点,该方法会将当前线程构造成节点,并加入到等待队列中,然后是否同步状态,唤醒同步队列中的后继节点,然后当前线程进入等待状态。

当等待队列中的节点被唤醒,则唤醒节点的线程开始尝试获取同步状态。如果不是通过其他线程调用Condition.signal()方法唤醒,而是对等待线程进行中断,则会抛出InterruptedException

3. 通知

调用Condition的signal()方法,将会唤醒在等待队列中的首节点,在唤醒节点之前,会将节点移到同步队列中。

        public final void signal() {
            if (!isHeldExclusively())//判断当前线程是否获取了锁
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
        private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
    final boolean transferForSignal(Node node) {
         //如果不能改变waitStatus,则取消唤醒
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

    //移动到同步队列并尝试设置前驱节点的waitStatus以命令线程(可能)等待。 如果取消或尝试设置waitStatus失败,唤醒以重新同步
       Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);//唤醒当前等待队列中节点的线程
        return true;
    }

通过调用同步器的enq(Node node)方法,等待队列中的头节点线程安全地移到同步队列中。当节点移到到同步队列后,当前线程在使用LockSupport唤醒该节点的线程。

被唤醒后的线程,将从await()方法中的while循环中退出(isOnSyncQueue(Node node)方法返回true,节点已经在同步队列中),进而调用同步器的acquireQueued()方法加入到获取同步状态的竞争中。

成功获取同步状态之后,被唤醒的线程将从先前调用的await()方法返回,此时该线程已经成功地获取了锁

Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal方法,效果就是将等待队列中所有节点全部转移到同步队列中,并唤醒每个节点的线程。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值