1.Condition
1.1Condition概述
位于JUC包中,用来对原生的wait、notify/notifyAll这些方法进行增强,从Java语言层面,实现类似的功能。其方法的命名也类似于wait/notify。
在AQS中有一个同步等待队列(双向队列) ,而在Condition中也有一个条件队列(单向队列) 。有一个Condition对象就表示有一个条件队列。
Object的wait和notify/notifyAll是与对象监视器配合完成的线程间的等待/通知机制。而Condition是和Lock配合完成等待通知机制。前者是Java底层级别的(wait/notify/notifyAll方法都是native方法,c++编写的,并且是由final修饰的,不可重写)。而后者是Java代码级别的实现,具体的实现在AQS中公共内部类ConditionObject中。
其中,属性firstWaiter表示条件队列的头结点(和AQS的同步等待对列不同的是:AQS初始化时的头结点是不封装线程的,也就是一个虚拟的头结点。但是在条件队列中初始化时的头结点是包含线程的,是从等待队列中出队后,再进入到条件队列中的。下面会进行具体的说明)。
lastWaiter是条件队列的尾结点。这和等待队列中的head和tail类似。此外,Condition是基于AQS的独占锁实现,没有共享锁实现。
1.3Condition的源码分析
1.3.1await()
在我们使用Condition时,一般会通过Lock.newCondition来得到一个Condition对象。然后当前线程再获取锁,当满足一定条件,线程需要等待时,那么调用await()。如图:
从上图可以看出,在调用await方法后,首先检查当前线程的中断状态,如果为true,那么就会抛出异常。
如果没有中断,才会将调用addConditionWaiter()方法把当前线程封装成一个结点,加入到条件队列的尾部。如果此时还没有同步队列,那么就在这个方法中初始化同步队列。初始化同步队列的方法也很简单:将当前线程封装成一个结点,并将firstWaiter和lastWaiter都指向这个结点。如果条件队列不为空,在这个过程中,也会对条件队列进行维护,如图:
这是在addConditionWaiter中调用的一个方法,可以看到在这个方法中会遍历条件队列中结点,如果发现结点的waitStatus!=CONDITION,那么就会将这个结点从条件对列中移除。
在将结点加入到条件队列中后,又会将当前线程所占有的锁释放,如果释放失败,那么就会抛出 IllegalMonitorStateException异常。所以,在使用Condition中的await和signal方法时,也和wait/notify一样,都需要获取到锁才能调用,不然就会出现异常。
在线程释放锁后,就会将线程挂起。如果想要线程退出这种状态,那么有两种方法:
- 线程重新回到同步等待队列中
- 线程发生了中断,break退出循环。
但是,要走这个循环,那么首先线程需要被允许调度,那么就要调用signal(),那么就将线程唤醒,再判断是否中断,如果中断,那么也会将结点加入到等待队列中。
跳出循环后,就调用AQS的acquireQueued()尝试排队获取锁,前面有节点,获取不到锁,就在等待队列中挂起。这就完成了出等到队列又回到等待队列的过程。
1.3.2 signal()
signal()方法就相对比较简单了。
首先先检查这个线程是不是持有锁,就是判断exclusiveOwnerThread保存的线程是不是当前线程,不是就抛出异常。所以,这里也可以体现出Condition的await/signal也是需要锁的。如果线程持有锁,那么使用自旋的CAS操作将这个结点的waitStatus从CONDITION修改为0。
如上图,将头结点出队后,循环调用transferForSignal(),修改waitStatus的值。修改成功,将这个条件队列中的头结点加入到等待队列中,并且把waitStatus的值修改为SIGNAL,最后唤醒这个结点的线程。这个结点的线程就会从await的循环中出来,执行下面的流程。
1.3.3 signalAll
signalAll就是将条件队列中的结点全部出队,加入到等待队列中。
1.3.4 不可中断的方法
awaitUninterruptibly方法仅仅只是改变中断标志,不会进行什么实际的操作。
1.3.5超时等待的方法
如果时间到了,那么立马将这个结点从条件队列中加入到等待队列中,和
wait相似。同时也是支持中断的。