Condition源码讲解

Condition源码讲解

简介

Condition是一个等待队列,作用类似与我们Object类中的wait()以及notify()方法,作用都是用于线程的唤醒与等待
Condition比我们在Object中的等待队列作用要更强大,更灵活,像我们的ConditionObject可以利用newCondition()来产生多个等待队列

下面来介绍Condition的源码与实现

首先是接口Condition,它定义了我们的Condition的基本功能


public interface Condition {

    //常用的线程挂起等待的方法
    void await() throws InterruptedException;

    
    void awaitUninterruptibly();

    long awaitNanos(long nanosTimeout) throws InterruptedException;

    boolean await(long time, TimeUnit unit) throws InterruptedException;

    boolean awaitUntil(Date deadline) throws InterruptedException;
	
	//唤醒的方法
    void signal();

    void signalAll();
}

然后重点介绍AQS中的Condition接口实现----ConditionObject

先从属性开始进行讲解

重要属性
	//头节点
    private transient AbstractQueuedSynchronizer.Node firstWaiter;
    //尾节点
    private transient AbstractQueuedSynchronizer.Node lastWaiter;

注意看,这里的节点的类型是我们的AQS中的Node类型,所以可以知道我们等待队列和同步队列中的节点是一样的,然后先来看看他的模型是怎样的
在这里插入图片描述

可以看到这个就是一个典型的双端队列,这里前继没有给出,其实是可以有指向前面的指针的,但是等待的线程就是等待者,只负责等待,唤醒的线程就是唤醒者,只负责唤醒,因此每次要执行唤醒操作的时候,直接唤醒等待队列的首节点就行了。等待队列的实现中不需要遍历队列,因此也不需要prev指针,所以在图上没有显示前继指针
每个Condition对象都包含着一个FIFO队列,该队列是Condition对象通知/等待功能的关键。
在队列中每一个节点都包含着一个线程引用,该线程就是在该Condition对象上等待的线程。.

然后来看看重要的方法

重要方法

await()方法

		public final void await() throws InterruptedException {
            //如果当前线程中断
            if (Thread.interrupted())
                throw new InterruptedException();
            //当前线程加入到等待队列
            AbstractQueuedSynchronizer.Node node = addConditionWaiter();
            //释放锁,等待会释放自己的锁
            int savedState = fullyRelease(node);

            int interruptMode = 0;

            /* **
             * 检测此节点的线程是否在同步队列上,即还没有被signal,则将当前线程阻塞
             * 如果不在,说明这个线程不具备竞争锁的资格
             * 知道检测到此节点到同步队列上
             */
            while (!isOnSyncQueue(node)) {
                //先挂起线程,这里会等待唤醒
                LockSupport.park(this);
                //如果已经中断了,直接退出
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 退出了上面自旋说明当前节点已经在同步队列上,但是当前节点不一定在同步队列队首。
            // acquireQueued将阻塞直到当前节点成为队首,
            // 即当前线程获得了锁。然后await()方法就可以退出了,让线程继续执行await()后的代码。
            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 AbstractQueuedSynchronizer.Node addConditionWaiter() {
            //获取到等待队列中的尾节点
            AbstractQueuedSynchronizer.Node t = lastWaiter;

            //如果Node的节点状态不是condition,说明这个节点不在等待状态
            if (t != null && t.waitStatus != AbstractQueuedSynchronizer.Node.CONDITION) {
                //需要清除掉等待队列中不是condition状态的节点
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //当前线程新建节点,状态是condition
            AbstractQueuedSynchronizer.Node node = new AbstractQueuedSynchronizer.Node(Thread.currentThread(), AbstractQueuedSynchronizer.Node.CONDITION);
            //判断头是不是空
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }
final int fullyRelease(AbstractQueuedSynchronizer.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 = AbstractQueuedSynchronizer.Node.CANCELLED;
            }
        }
		final boolean isOnSyncQueue(AbstractQueuedSynchronizer.Node node) {
            //如果节点状态是condition或者前驱节点是null,返回false
            if (node.waitStatus == AbstractQueuedSynchronizer.Node.CONDITION || node.prev == null)
                return false;
            //如果后继系欸但部位null,那么肯定在同步队列中
            if (node.next != null)
                return true;

            return findNodeFromTail(node);
        }
		/* **
         * 删除掉条件队列中不为condition的节点
         * @param
         * @return void
         * @author zengyiwen
         * @date 16:31 2022/1/18
         */
        private void unlinkCancelledWaiters() {
            AbstractQueuedSynchronizer.Node t = firstWaiter;
            AbstractQueuedSynchronizer.Node trail = null;
            //遍历节点
            while (t != null) {
                AbstractQueuedSynchronizer.Node next = t.nextWaiter;
                if (t.waitStatus != AbstractQueuedSynchronizer.Node.CONDITION) {
                    t.nextWaiter = null;
                    if (trail == null)
                        firstWaiter = next;
                    else
                        trail.nextWaiter = next;
                    if (next == null)
                        lastWaiter = trail;
                }
                else
                    trail = t;
                t = next;
            }
        }

总的来说,流程就是这样的
1、 获取了锁的线程调用我们的await()方法,
2、 将自己的线程封装成Node节点然后加入到等待队列
3、 释放自己的资源,
4、 自旋判断自己的是不是被signal()了,也就是是不是出现在我们的同步队列了,没有出现的话代表没人唤醒,继续等待
5、 如果被唤醒了,因为自己的资源已经被释放了,所以自己需要继续抢占资源,所以又会加入到同步队列
6、 清除掉等待队列中不处于等待状态的节点

然后来看看我们的唤醒流程

		/* **
         * 通知方法
         *  判断当前线程是否已经获取了锁,如果没有获取则直接抛出异常,因为获取锁为通知的前置条件。
         *  如果线程已经获取了锁,则将唤醒条件队列的首节点
         *  唤醒首节点是先将条件队列中的头节点移出,然后调用AQS的enq(Node node)方法将其安全地移到CLH同步队列中
         *  最后判断如果该节点的同步状态是否为Cancel,或者修改状态为Signal失败时,则直接调用LockSupport唤醒该节点的线程。
         * @param []
         * @return void
         * @author zengyiwen
         * @date 16:32 2022/1/18
         */
        public final void signal() {
            //检测当前线程是不是拥有锁
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            //唤醒条件队列中的第一个节点
            AbstractQueuedSynchronizer.Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
		/* **
         * 1.修改头节点
         * 2.调用transferForSignal将节点移到CLH同步队列
         * @param
         * @return void
         * @author zengyiwen
         * @date 16:34 2022/1/18
         */
        private void doSignal(AbstractQueuedSynchronizer.Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                    (first = firstWaiter) != null);
        }

负责将节点加入到同步队列并且唤醒线程

		final boolean transferForSignal(AbstractQueuedSynchronizer.Node node) {
            //将节点变为初始状态0,CAS修改
            if (!compareAndSetWaitStatus(node, AbstractQueuedSynchronizer.Node.CONDITION, 0))
                return false;

            //加入到SYN队列中,
            AbstractQueuedSynchronizer.Node p = enq(node);

            //获取到节点的状态
            int ws = p.waitStatus;
            //如果节点的状态是cancel,或者CAS失败,就会直接唤醒
            if (ws > 0 || !compareAndSetWaitStatus(p, ws, AbstractQueuedSynchronizer.Node.SIGNAL))
                LockSupport.unpark(node.thread);
            return true;
        }

还有singnalAll()

		public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            AbstractQueuedSynchronizer.Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
        }
        private void doSignalAll(AbstractQueuedSynchronizer.Node first) {
            lastWaiter = firstWaiter = null;
            do {
                AbstractQueuedSynchronizer.Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
        }

唤醒就很简单了,就是尝试修改等待队列的头节点,同时将节点移到同步队列中去获取资源,并且调用unpark方法,让他继续去执行await方法中的后半部分,因为在await中被阻塞了,所以后面的流程就是调用aquiredQueue()方法来进行自旋获取锁

Condition的原理

Condition的本质就是等待队列和同步队列的交互:

当一个持有锁的线程调用Condition.await()时,它会执行以下步骤:

1、 构造一个新的等待队列节点加入到等待队列队尾
2、 释放锁,也就是将它的同步队列节点从同步队列队首移除
3、 自旋,直到它在等待队列上的节点移动到了同步队列(通过其他线程调用signal())或被中断
4、 阻塞当前节点,直到它获取到了锁,也就是它在同步队列上的节点排队排到了队首。

当一个持有锁的线程调用Condition.signal()时,它会执行以下操作:
1、 从等待队列的队首开始,尝试对队首节点执行唤醒操作;如果节点CANCELLED,就尝试唤醒下一个节点;如果再CANCELLED则继续迭代。

2、 对每个节点执行唤醒操作时,首先将节点加入同步队列,此时await()操作的步骤3的解锁条件就已经开启了。然后分两种情况讨论:

3、 如果先驱节点的状态为CANCELLED(>0) 或设置先驱节点的状态为SIGNAL失败,那么就立即唤醒当前节点对应的线程,此时await()方法就会完成步骤3,进入步骤4.
4、 如果成功把先驱节点的状态设置为了SIGNAL,那么就不立即唤醒了。等到先驱节点成为同步队列首节点并释放了同步状态后,会自动唤醒当前节点对应线程的,这时候await()的步骤3才执行完成,而且有很大概率快速完成步骤4.

总结

为什么同步队列是双向的,而等待队列是单向的呢?

之所以同步队列要设计成双向的,是因为在同步队列中,节点唤醒是接力式的,由每一个节点唤醒它的下一个节点,如果是由next指针获取下一个节点,是有可能获取失败的,因为虚拟队列每添加一个节点,是先用CAS把tail设置为新节点,然后才修改原tail的next指针到新节点的。因此用next向后遍历是不安全的,但是如果在设置新节点为tail前,为新节点设置prev,则可以保证从tail往前遍历是安全的。因此要安全的获取一个节点Node的下一个节点,先要看next是不是null,如果是null,还要从tail往前遍历看看能不能遍历到Node。

而等待队列就简单多了,等待的线程就是等待者,只负责等待,唤醒的线程就是唤醒者,只负责唤醒,因此每次要执行唤醒操作的时候,直接唤醒等待队列的首节点就行了。等待队列的实现中不需要遍历队列,因此也不需要prev指针。

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值