Condition的实现分析

对比Object的监视器方法和Condition接口

对比项Object Monitor MethodsCondition
前置条件获取对象的锁

调用Lock.lock()

调用Lock.newCondition()获取Condition对象

调用方式直接调用,如:object.wait()直接调用,如:condition.await()
等待队列个数一个多个
当前线程释放锁并进入等待状态支持支持
当前线程释放锁并进入等待状态,在等待状态中不响应中断不支持支持
当前线程释放锁并进入超时等待状态支持支持
当前线程释放锁并进入等待状态到将来的某个时间不支持支持
唤醒等待队列中的一个线程支持支持
唤醒等待队列中的全部线程支持支持
public interface Condition {

     // 当前线程进入等待状态直到被通知(signal)或被中断
    void await() throws InterruptedException;
    // 不响应中断等待,直到被通知(signal)
    void awaitUninterruptibly();
    // 等待指定时长直到被通知或中断或超时。
    long awaitNanos(long nanosTimeout) throws InterruptedException;
    // 等待指定时长直到被通知或中断或超时。
    boolean await(long time, TimeUnit unit) throws InterruptedException;
    // 当前线程进入等待状态直到被通知、中断或者到某个时间。如果没有到指定事件就被通知,方法返回true,否则false。 
    boolean awaitUntil(Date deadline) throws InterruptedException;
    // 唤醒一个等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁  
    void signal();
    // 唤醒所有等待在Condition上的线程,该线程从等待方法返回前必须获得与Condition相关联的锁  
    void signalAll();
}

 

Condition实现分析

    每个Condition对象都包含着一个队列(以下称为等待队列),该队列是Condition对象实现等待/通知功能的关键。

等待队列

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

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

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


    Condition拥有首尾节点的引用,而新增节点只需要将原有的尾节点nextWaiter 指向它,并且更新尾节点即可。上述节点引用更新的过程并没有使用CAS保证,原因在于调用 await()方法的线程必定是获取了锁的线程,也就是说该过程是由锁来保证线程安全的。
    在Object的监视器模型上,一个对象拥有一个同步队列和等待队列,而并发包中的 Lock(更确切地说是同步器)拥有一个同步队列和多个等待队列。

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

等待

    调用Condition的await()方法(或者以await开头的方法),会使当前线程进入等待队列并释放锁,同时线程状态变为等待状态。当从await()方法返回时,当前线程一定获取了Condition相关联的锁。

    如果从队列(同步队列和等待队列)的角度看await()方法,当调用await()方法时,相当于同 步队列的首节点(获取了锁的节点)移动到Condition的等待队列中。


        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);
                //检查阻塞过程中是否发生中断,若有中断则退出while循环
                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);
        }


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

    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }

    调用该方法的线程成功获取了锁的线程,也就是同步队列中的首节点,该方法会将当前线程构造成节点并加入等待队列中,然后释放同步状态,唤醒同步队列中的后继节点,然后当前线程会进入等待状态。当等待队列中的节点被唤醒,则唤醒节点的线程开始尝试获取同步状态。如果不是通过其他线程调用Condition.signal()方法唤醒,而是对等待线程进行中断,则会抛出 InterruptedException。

通知

    调用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;
        //将节点从条件队列传输到同步队列。如果成功返回true。
            } while (!transferForSignal(first) &&//
                     (first = firstWaiter) != null);
        }

    final boolean transferForSignal(Node node) {
       
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        // 将当前node加入到同步队列中,等待获取锁
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            //通知前驱节点,在释放状态唤醒。
            LockSupport.unpark(node.thread);
        return true;
    }

    调用signal方法的前置条件是当前线程必须获取了锁,可以看到signal()方法进行了 isHeldExclusively()检查,也就是当前线程必须是获取了锁的线程。接着获取等待队列的首节点,将其移动到同步队列并使用LockSupport唤醒节点中的线程。

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

    被唤醒后的线程,将从await()方法中的while循环中退出(isOnSyncQueue(Node node)方法返回true,节点已经在同步队列中),进而调用同步器的acquireQueued()方法加入到获取同步状态的竞争中。
    成功获取同步状态(或者说锁)之后,被唤醒的线程将从先前调用的await()方法返回,此时该线程已经成功地获取了锁。
    Condition的signalAll()方法,相当于对等待队列中的每个节点均执行一次signal()方法,效果就是将等待队列中所有节点全部移动到同步队列中,并唤醒每个节点的线程。

 

参考《Java并发编程的艺术》

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值