juc之condition

前言

之前学synchronized的时候,我们肯定就学了Object类上面的wait 和 notify,notifyAll方法,这两个方法呢,是需要获取了对象锁才能进行使用的,因为如果线程都没有获取到资源,那么执行wait方法又还有什么意义呢?没获取锁,线程本身就在等待。
也由此可见,wait 和notify都要依赖于获取到了某个对象的锁才能进行操作。这也极大的限制了灵活性,而lock锁不会有这
个缺点,它可以构建多个等待队列

condition

我们看到Condition的这个接口,发现其有两个实现,一个是在aqs中 一个是在aqls中,分别有着实现
condition中定义了 7个接口方法,大概分为两类,一个是获取,一个是唤醒
在这里插入图片描述condition这个类中,官方注释还给出了一个数组长度为100的有界缓冲的实现,利用condition进行实现的

    class BoundedBuffer {
        final Lock lock = new ReentrantLock();
        final Condition notFull  = lock.newCondition();
        final Condition notEmpty = lock.newCondition();

        final Object[] items = new Object[100];
        int putptr, takeptr, count;

        public void put(Object x) throws InterruptedException {
            lock.lock();
            try {
                while (count == items.length)
                    notFull.await();
                items[putptr] = x;
                if (++putptr == items.length) putptr = 0;
                ++count;
                notEmpty.signal();
            } finally {
                lock.unlock();
            }
        }

        public Object take() throws InterruptedException {
            lock.lock();
            try {
                while (count == 0)
                    notEmpty.await();
                Object x = items[takeptr];
                if (++takeptr == items.length) takeptr = 0;
                --count;
                notFull.signal();
                return x;
            } finally {
                lock.unlock();
            }
        }
    }

下面我们aqs 对于condition的实现 是怎么实现的
Condition会在调用await()时将其包装成一个Node放入到条件队列中,但值得注意的是这个条件队列是基于单向链表实现,以nextWaiter来标记下一个节点的指针。

与同步队列恰好相反,条件队列并不会使用prev、next来串联链表,而是使用nextWaiter。

await()

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            //将当前线程加入到condition队列尾部,把waitStatus 不等于condition的结点移出链表外
            Node node = addConditionWaiter();
            // 同步队列首节点释放资源,唤醒下一个结点
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                // 简单来说,该方法的返回值代表当前线程是否在park的时候被中断唤醒,如果为true表示中断在signal调用之前,signal还未执行,那么这个时候会根据await的语义,在await时遇到中断需要抛出interruptedException,返回true就是告诉checkInterruptWhileWaiting返回THROW_IE(-1)。
如果返回false,否则表示signal已经执行过了,只需要重新响应中断即可
                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);
        }

看到addConditionWaiter()方法

private Node addConditionWaiter() {
            Node t = lastWaiter;
            // If lastWaiter is cancelled, clean out.
            if (t != null && t.waitStatus != Node.CONDITION) {
                // 把waitStatus 不等于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;
        }

isOnSyncQueue(Node node)检查这个节点是不是在同步队列

final boolean isOnSyncQueue(Node node) {
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
           // 同步队列 从尾部往上找 看是否在同步队列中
        return findNodeFromTail(node);
    }

signal()唤醒方法

看到了前面的await方法,如果线程被挂起了,那么唤醒的时候,应该做什么呢?可以应该把线程加入到同步队列中,但是应该也要判断一下waitstatus,

 public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }

doSignal(Node first)

唤醒队列中的第一个节点

private void doSignal(Node first) {
//这个循环会进入两次
            do {
                //因为要释放了,所以下一个结点置为firstWaiter,如果为空,直接将lastWaiter置空,因为代表着之前队列中只有这一个结点
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                // 因为要释放了 所以nextWaiter置空,方便gc    
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }

transferForSignal(first)

在transferForSignal方法中,我们先使用CAS操作将当前节点的waitStatus状态由CONDTION设为0,如果修改不成功,则说明该节点已经被CANCEL了,或者已经加入到了同步队列中了,则我们直接返回,操作下一个节点;如果修改成功,则说明我们已经将该节点从等待的条件队列中成功“唤醒”了,但此时该节点对应的线程并没有真正被唤醒,它还要和其他普通线程一样去争锁,因此它将被添加到sync queue的末尾等待获取锁。

我们这里通过enq方法将该节点添加进sync queue的末尾。关于该方法,我们在逐行分析AQS源码——独占锁的获取中已经详细讲过了,这里不再赘述。不过这里尤其注意的是,enq方法将node节点添加进队列时,返回的是node的前驱节点。

在将节点成功添加进sync queue中后,我们得到了该节点在sync queue中的前驱节点。我们前面说过,在sync queque中的节点都要靠前驱节点去唤醒,所以,这里我们要做的就是将前驱节点的waitStatus设为Node.SIGNAL, 这一点和shouldParkAfterFailedAcquire所做的工作类似:,
如果设置失败了,所不同的是,shouldParkAfterFailedAcquire将会向前查找,跳过那些被cancel的节点,然后将找到的第一个没有被cancel的节点的waitStatus设成SIGNAL,最后再挂起。而在transferForSignal中,当前Node所代表的线程本身就已经被挂起了,所以这里做的更像是一个复合操作——只要前驱节点处于被取消的状态或者无法将前驱节点的状态修成Node.SIGNAL,那我们就将Node所代表的线程唤醒,但这个条件并不意味着当前lock处于可获取的状态,有可能线程被唤醒了,但是锁还是被占有的状态,不过这样做至少是无害的,因为我们在线程被唤醒后还要去争锁,如果抢不到锁,则大不了再次被挂起。

值得注意的是,transferForSignal是有返回值的,但是我们在这个方法中并没有用到,它将在signal()方法中被使用。

    final boolean transferForSignal(Node node) {
        //去修改当前结点的状态,可能为false
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
        //如果修改当前结点的状态成功,则去请求加入到同步队列的尾部
        Node p = enq(node);
        int ws = p.waitStatus;
        // 如果被取消了,或者是将
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }

下面再来讲讲signalAll()方法,应该就是遍历的去进行释放,加入到同步队列的尾部,判断条件是nextWaiter() 为空

signalAll()

public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
        }

看代码就知道 和上面的分析一样

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

再来看看condition中其他的await方法
await方法是支持中断的,看看

awaitUninterruptibly()

是不支持中断的,await方法支持中断 是有个checkInterruptWhileWaiting(node)和 reportInterruptAfterWait(interruptMode)方法去对中断进行处理。
再来看看awaitUninterruptibly() 这个方法,并没有支持中断,只是遇见中断后,会重新打上中断标记

 public final void awaitUninterruptibly() {
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            boolean interrupted = false;
            while (!isOnSyncQueue(node)) {
                LockSupport.park(this);
                if (Thread.interrupted())
                    interrupted = true;
            }
            if (acquireQueued(node, savedState) || interrupted)
                selfInterrupt();
        }

long awaitNanos(long nanosTimeout)

加入了超时机制,如果超过了一定的时间,自动加入到同步队列中,且其支持中断

        public final long awaitNanos(long nanosTimeout)
                throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            final long deadline = System.nanoTime() + nanosTimeout;
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
                if (nanosTimeout <= 0L) {
                    transferAfterCancelledWait(node);
                    break;
                }
                if (nanosTimeout >= spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
                nanosTimeout = deadline - System.nanoTime();
            }
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null)
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
            return deadline - System.nanoTime();
        }

对比await方法,差不多就多了这些代码

 if (nanosTimeout <= 0L) {
                    transferAfterCancelledWait(node);
                    break;
                }
                if (nanosTimeout >= spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);

如果能够修改状态成功,说明还没有执行signal方法,就去加入到同步队列尾部,如果已经在队列中了就直接返回false,如果等待的时间小于1000纳秒,那么挂起也没啥意义,时间太短了。

   final boolean transferAfterCancelledWait(Node node) {
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);
            return true;
        }
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }

awaitUntil(Date deadline)

还有个awaitUntil这个方法 和 awaitNanos是差不多的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值