java.util.concurrent 之 Condition

java.util.concurrent最初的一个目标,就是消除之前java使用synchronized与Object的各种监视器方法(notify(),wait())等等来实现线程之前的各种同步与通信的不变与低效,我们现在知道,j.u.c中实现了AQS,并通过AQS实现了一些锁比如我们讲到的ReentrantLock。

Lock对象的lock()方法就相当于synchronized,但J.U.C的Lock对象当然不能和notify(),wait()等监视器方法直接相互作用,J.U.C必须要有一套自己的机制来实现这些监视器方法,这套机制通过Condition类来实现。

我们先来看看Condition定义的一些通用接口:

public interface Condition
{
    //当前线程进入等待状态,看到这个throw就应该知道是相应中断的
    public abstract void await()
        throws InterruptedException;
    //线程进入等待状态,并不相应中断
    public abstract void awaitUninterruptibly();
    //线程进入等待状态,超时则报出被中断
    public abstract long awaitNanos(long l)
        throws InterruptedException;
    //线程将一直等待直到线程被中断,或超时、或被signal()
    public abstract boolean await(long l, TimeUnit timeunit)
        throws InterruptedException;
    //线程等待的deadline形式,一直等待,等到到date那个时候,或者中途被中断
    public abstract boolean awaitUntil(Date date)
        throws InterruptedException;
    //类似notify()唤醒某一个被当前Condition对象的Lock对象锁住的线程
    public abstract void signal();
    //类似notifyAll()唤醒所有被当前Condition对象的Lock对象锁住的线程
    public abstract void signalAll();
}

看起来都非常舒服,和Object类的监视器方法对应的很好。

从这些接口中我们要知道推断出一个背景,就是每一个Condition对象都需要和一个Lock对象关联起来。比如下面的代码

ReentrantLock lock = new ReentrantLock();
Condition condition = lock.newCondition();

lock.lock();

System.out.println("等我10秒钟");
long before = System.currentTimeMillis();
condition.await(10, TimeUnit.SECONDS);
long after = System.currentTimeMillis();
long time = ((after - before) / 1000);
System.out.println("等了" + time + "s");

输出:

等我10秒钟
等了10s

可以看到这个condition一定要lock.newCondition()这种方式获取,事实上也只能通过这种方式获取,因此J.U.C将Condition作为AQS的内部类来实现。

下面我们来看看condition中最典型的两个方法,await()和signal(),顺带提一下signalAll().

对于await方法来说,我们通过对AQS的了解,大致也可以猜到Condition也是通过一个队列来处理被await()的线程:

 private transient Node firstWaiter;
 private transient Node lastWaiter;

这个队列也是以AQS中定义的Node作为节点,需要注意的是,Condition的所有代码只用到了Node的nextWaiter并没有用到prev和next,这个一是为了万一有Node同时被AQS和Condition使用,能够防止Node的指针同时被Condition和AQS本身操作,即隔离Condition和AQS的操作;二是由于只定义了一个nextWaiter,并没有定义什么preWaiter,实现的是一个单向链表的队列,AQS的CLH队列则是双向链表实现的。

下面是await()方法的实现:

public final void await()
            throws InterruptedException
        {
            //判断线程是否中断
            if(Thread.interrupted())
                throw new InterruptedException();
            //将线程加入Condition的等待队列
            Node node = addConditionWaiter();
            //释放condition,唤醒condition等待队列中的下一个节点
            long l = fullyRelease(node);
            int i = 0;

            do
            {
                //检查线程是否在AQS的CLH队列中,如果在,
                //则参与锁的竞争,不用将线程挂起
                if(isOnSyncQueue(node))
                    break;
                //线程被Condition挂起
                LockSupport.park(this);
            } while((i = checkInterruptWhileWaiting(node)) == 0);
            //如果参与锁的竞争,尝试自旋地获取锁
            if(acquireQueued(node, l) && i != -1)
                i = 1;
            //剔除所有被Cancelled线程
            if(node.nextWaiter != null)
                unlinkCancelledWaiters();
            //根据被中断的状态来处理线程的中断
            if(i != 0)
                reportInterruptAfterWait(i);
        }

这里比较难理解的就是checkInterruptWhileWaiting(node)这个方法,这个方法是这样写的:

/**
         * Checks for interrupt, returning THROW_IE if interrupted
         * before signalled, REINTERRUPT if after signalled, or
         * 0 if not interrupted.
         */
        private int checkInterruptWhileWaiting(Node node) {
            return (Thread.interrupted()) ?
                ((transferAfterCancelledWait(node))? THROW_IE : REINTERRUPT):0;
        }

从注释可以看到,在线程中断的情况下,如果这个线程在被signal前中断,返回THROW_IE ,如果在被signal后中断,返回REINTERRUPT,未被中断则返回0。
(transferAfterCancelledWait(node)这个方法主要是用于在已经判断线程为中断的情况下,判断线程的中断是在signal前还是signal后,我们来看看具体是怎么实现的。

/**
     * Transfers node, if necessary, to sync queue after a cancelled
     * wait. Returns true if thread was cancelled before being
     * signalled.
     * @param current the waiting thread
     * @param node its node
     * @return true if cancelled before the node was signalled.
     */
    final boolean transferAfterCancelledWait(Node node) {
        //当Node的状态为Contion,尝试将Node的状态设置为0
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            //设置成功后进入AQS同步队列
            enq(node);
            //表示该线程在signal前就被interrupt了
            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.
         */
        //如果线程不在AQS同步队列中
        while (!isOnSyncQueue(node))
            //线程让步
            Thread.yield();
        //表示线程在被signal后才被interrupt
        return false;
    }

这段代码有点高能,有点像高数里面那些‘显然’、‘易证得’,我们来研究一下,其实写这篇博客的时候我也不是很懂,一起学习,一起学习。
首先我们要知道这个线程已经是被中断了,compareAndSetWaitStatus(node, Node.CONDITION, 0)这一步,我想应该是做一个判断,由于signal操作时,也会将node 的status设置为0,所以如果此时能够对status进行期望值为CONDITION的CAS操作,那肯定是没有发生signal的,而我已经判断到了线程已经中断了,所以线程中断发生在signal之前,返回true。如果CAS操作失败呢,那我们只能说,不一定,我们能知道的只是signal已经发生,中断也已经发生。signal发生了,那一定有一刻,他会进入AQS的同步队列去竞争锁,这一点在后面讲signal时也能看到,所以就自旋地检查自己是否在AQS同步队列中,如果不在,让步给其他线程执行。

await后面的一句reportInterruptAfterWait(i)就是根据transferAfterCancelledWait(node)的返回值来处理线程的中断,我们可以来看看

 private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }
/**
* Convenience method to interrupt current thread
*/
private static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

如果transferAfterCancelledWait(node)返回了THROW_IE,直接抛出异常,如果返回REINTERRUPT,则后续还需要对当前线程调用一次interrupt(),为什么这里需要再调用一次呢?这是因为我们await最初做了一个判断:

 //判断线程是否中断
 if(Thread.interrupted())

这个interrupted()方法,不仅能够判断线程是否中断,还会将线程的中断标志清除,我们的REINTERRUPT就是为了补上这个标志。

下面我们来看signal()方法

/**
         * Moves the longest-waiting thread, if one exists, from the
         * wait queue for this condition to the wait queue for the
         * owning lock.
         *
         * @throws IllegalMonitorStateException if {@link #isHeldExclusively}
         *         returns {@code false}
         */
        public final void signal() {
            //判断当前线程是否拥有锁
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //对Condition等待队列中的第一个节点进行signal操作
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
/**
         * Removes and transfers nodes until hit non-cancelled one or
         * null. Split out from signal in part to encourage compilers
         * to inline the case of no waiters.
         * @param first (non-null) the first node on condition queue
         */
        private void doSignal(Node first) {
            do {
                //当前节点的后继节点为null
                if ( (firstWaiter = first.nextWaiter) == null)
                    //队列置空
                    lastWaiter = null;
                //当前节点从队列中脱离出来
                first.nextWaiter = null;
            }
            //处理signal的一些状态转换 
            while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
/**
     * Transfers a node from a condition queue onto sync queue.
     * Returns true if successful.
     * @param node the node
     * @return true if successfully transferred (else the node was
     * cancelled before signal).
     */
    final boolean transferForSignal(Node node) {
        /*
         * If cannot change waitStatus, the node has been cancelled.
         */
        //对node的状态进行CAS操作,如果他不是CONDITION状态,
        //则一定是CANCELLED状态,这里等下说到
        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).
         */
        //线程进入AQS同步队列竞争锁状态,返回前驱
        Node p = enq(node);
        //检查前驱状态,如果前驱状态无法被设置为signal,
        //或已被cancelled或已占有锁
        int c = p.waitStatus;
        //如果发现前驱节点等待状态无法置为signal
        if (c > 0 || !compareAndSetWaitStatus(p, c, Node.SIGNAL))
            //立即唤醒当前节点,从之前的await中的循环中unpark出来,
            //并执行await接下来的acquireQueued操作,即获取锁操作
            //这个操作不管node在队列中是否为头部,直接参与锁的竞争
            LockSupport.unpark(node.thread);
        //否则将node放在队列中,等待成功获取锁的头结点来唤醒,即在AQS同步队列中
        //acquire操作的时候,也即,Lock对象的unlock操作的时候,唤醒这个节点
        return true;
    }

我们要理解一点,就是如果一个node进入了AQS的等待队列,他迟早要被unpark或interrupt,因此,signal操作只需要将node放入AQS队列让它去争抢锁状态就可以了,但如果它在AQS同步队列中的前驱已被cancelled或者无法唤醒,那就立刻unpark,从await中的while循环中出来,进行锁的争抢,至于为什么要这样,也许是因为性能的原因吧。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值