这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的 (7)

在Condition队列中插入对应的结点后,线程A会释放所持有的资源,走到while循环那层逻辑,

while (!isOnSyncQueue(node)) { LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0) break; }

isOnSyncQueue方法的会判断当前的线程节点是不是在同步队列中,这个时候此结点还在Condition队列中,所以该方法返回false,这样的话循环会一直持续下去,线程被挂起,等待被唤醒,此时,线程A的流程暂时停止了。

当线程A调用await()方法挂起的时候,线程B获取到了线程A释放的资源,然后执行signal()方法:

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

先判断当前线程是否为获取锁的线程,如果不是则直接抛出异常。 接着调用doSignal()方法来唤醒线程。

private void doSignal(Node first) { // 循环,从队列一直往后找不为空的首结点 do { if ( (firstWaiter = first.nextWaiter) == null) lastWaiter = null; first.nextWaiter = null; } while (!transferForSignal(first) && (first = firstWaiter) != null); } final boolean transferForSignal(Node node) { // CAS循环,将结点的waitStatus改为0 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; }

从doSignal的代码中可以看出,这时候程序寻找的是Condition等待队列中首结点firstWaiter的结点,此时该结点指向的是线程A的结点,所以之后的流程作用的都是线程A的结点。

这里分析下transferForSignal方法,先通过CAS自旋将结点waitStatus改为0,然后就把结点放入到同步队列 (此队列不是Condition的等待队列) 中,然后再用CAS将同步队列中该结点的前驱结点waitStatus改为Node.SIGNAL,也就是-1,此时AQS的数据结构大概如下 (额.....少画了个箭头,大家就当head结点是线程A结点的前驱结点就好):

这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的

回到await()方法,当线程A的结点被加入同步队列中时,isOnSyncQueue()会返回true,跳出循环,

while (!isOnSyncQueue(node)) { LockSupport.park(this); 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);

接着执行acquireQueued()方法,这里就不用多说了吧,尝试重新获取锁,如果获取锁失败继续会被挂起,直到另外线程释放锁才被唤醒。

所以,当线程B释放完锁后,线程A被唤醒,继续尝试获取锁,至此流程结束。

对于这整个通信过程,我们可以画一张流程图展示下:

这才是图文并茂:我写了1万多字,就是为了让你了解AQS是怎么运行的

总结

说完了Condition的使用和底层运行机制,我们再来总结下它跟普通 wait/notify 的比较,一般这也是问的比较多的,Condition大概有以下两点优势:

Condition 需要结合 Lock 进行控制,使用的时候要注意一定要对应的unlock(),可以对多个不同条件进行控制,只要new 多个 Condition对象就可以为多个线程控制通信,wait/notify 只能和 synchronized 关键字一起使用,并且只能唤醒一个或者全部的等待队列;

Condition 有类似于 await 的机制,因此不会产生加锁方式而产生的死锁出现,同时底层实现的是 park/unpark 的机制,因此也不会产生先唤醒再挂起的死锁,一句话就是不会产生死锁,但是 wait/notify 会产生先唤醒再挂起的死锁。

最后

对AQS的源码分析到这里就全部结束了,虽然还有很多知识点没讲解,比如公平锁/非公平锁下AQS是怎么作用的,篇幅所限,部分知识点没有扩展还请见谅,尽管如此,如果您能看完文章的话,相信对AQS也算是有足够的了解了。

回顾本篇文章,我们不难发现,无论是独占还是共享模式,或者结合是Condition工具使用,AQS本质上的同步功能都是通过对锁和队列中结点的操作来实现的,从设计上讲,AQS的组成结构并不算复杂,底层的运转机制也不会很绕,所以,大家如果看源码的时候觉得有些困难的话也不用灰心,多看几遍,顺便画个图之类的,理清下流程还是没什么问题的。

内容版权声明:除非注明,否则皆为本站原创文章。

转载注明出处:https://www.heiqu.com/wpjfxf.html