点1:AQS即AbstractQueuedSynchronizer,内部维护了一个CLH队列,在JDK的lock实现中深受重用,详细介绍请看,[http://www.importnew.com/22102.html]
点2:Condition,JDK API中是这样解释的:Condition 将 Object 监视器方法(wait、notify 和 notifyAll)分解成截然不同的对象,以便通过将这些对象与任意 Lock 实现组合使用,为每个对象提供多个等待 set(wait-set)。其中,Lock 替代了 synchronized 方法和语句的使用,Condition 替代了 Object 监视器方法的使用。
看过ReentrantLock源码的朋友对这两位应该不陌生,今天就和大家一起学习一下这两个兄弟之间的CP暧昧。
Condition的使用总是伴随着Lock的使用,先跑一个例子给大家:
执行结果如下
一、先来分析一下 await()方法
(1)addConditionWaiter()将当前线程包装为node维护在 condition自己的队列中
(2)fullyRelease(node)将当前线程已经获取到的 lock释放
(3)while 遍历AQS的队列,看当前节点是否在队列(注意此时的队列已经是AQS的队列)中,不在 说明它还没有竞争锁的资格,所以继续将自己沉睡。直到它被加入到队列(AQS队列)中,那么什么时候被加入队列吗?这里先卖个关子。
(4)被唤醒后,重新开始正式竞争锁,同样,如果竞争不到还是会将自己沉睡,等待唤醒重新开始竞争。
二、接下来我们看看signal方法,firstWaiter为condition自己维护的一个链表的头结点,取出第一个节点后开始唤醒操作
(1)着重看一下doSignal()
(2)继续往下走,将老的头结点,加入到AQS的等待队列中,这就是上边卖的关子。你一定恍然大悟吧?线程2发出signal信号后,线程1就具备竞争锁的条件了,这时候线程1就会被唤醒加入到AQS队列中;
可以看到,正常情况 ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL)这个判断是不会为true的,所以,不会在这个时候唤醒该线程。只有到发送signal信号的线程用reentrantLock.unlock()后因为它已经被加到AQS的等待队列中,所以才会被唤醒。
接下来总结一下流程:
线程waitThread 先执行调用reentrantLock.lock,获取到了锁,此时线程signalThread也去申请锁时候被加入到AQS的等待队列中。
线程waitThread调用await方法时释放了锁。
接着waitThread马上被加入到Condition的等待队列中,意味着该线程需要signal信号。
线程signalThread,因为线程waitThread释放锁的关系,被唤醒,并判断可以获取锁,于是线程signalThread获取锁。
线程signalThread调用signal方法,这个时候Condition的等待队列中只有线程waitThread一个节点,于是它被唤醒,去竞争锁,此时线程signalThread还未释放锁,所以只能加入到AQS的等待队列中。
signal方法执行完毕,线程signalThread调用reentrantLock.unLock()方法,释放锁。这个时候因为AQS中只有线程waitThread,于是,AQS释放锁后按从头到尾的顺序唤醒线程时,线程waitThread被唤醒,于是线程waitThread继续执行。
直到释放所整个过程执行完毕。
总的来看:整个过程是AQS 和Condition这哥俩的等待队列相互移动处理来实现的,Condition做为条件类,内部维护了一个等待队列,在合适的机会将自己的队列的节点移除并将设置到AQS队列中,让其去争夺lock,Condition拥有修改AQS队列的特权。