AQS源码分析
AQS源码分析,看这篇博客就够了,绝对的详细流弊
https://www.cnblogs.com/waterystone/p/4920797.html
但是我读了好几遍,这个方法还是不太理解,得注重再讲解一下:
private void doReleaseShared() { for (;;) { Node h = head; 拿到头节点,已经是刚唤醒的节点了 if (h != null && h != tail) { 如果为空或者是队尾则不在唤醒 int ws = h.waitStatus; if (ws == Node.SIGNAL) { //第一次进来,必定是这个状态,因为我们知道shouldParkAfterFailedAcquire必定是这个状态才能休息 if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0)) //将状态设置成0,第二次循环过来,好走else if这个很关键 continue; unparkSuccessor(h);//唤醒后继 } else if (ws == 0 && !compareAndSetWaitStatus(h, 0, Node.PROPAGATE)) 第一次唤醒后继节点了,如果head的节点没变,说明还在阻塞,修改状态退出 continue; } if (h == head)// head发生变化 ,发生变化则直接退出 break; } }
大概流程:
调用这个方法有两个地方:
1:tryReleaseShared()方法,释放资源的时候
2:doAcquireShard()方法中,释放资源后,set头节点也会进行调用
第一步:先将状态设置成0,然后唤醒下一个节点,这个时候,下一个节点如果走的比较快,那么一定会重新设置了head节点,则直接退出
第二步:如果唤醒的后继节点还没走到,则for循环第二次,会将节点的状态设置成 PROPAGATE,然后就一直循环判断头节点有没有变化,退出即可。
方法:shouldParkAfterFailedAcquire
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { int ws = pred.waitStatus;//拿到前驱的状态 if (ws == Node.SIGNAL) //如果已经告诉前驱拿完号后通知自己一下,那就可以安心休息了 return true; if (ws > 0) { /* * 如果前驱放弃了,那就一直往前找,直到找到最近一个正常等待的状态,并排在它的后边。 * 注意:那些放弃的结点,由于被自己“加塞”到它们前边,它们相当于形成一个无引用链,稍后就会被保安大叔赶走了(GC回收)! */ do { node.prev = pred = pred.prev; } while (pred.waitStatus > 0); pred.next = node; } else { //如果前驱正常,那就把前驱的状态设置成SIGNAL,告诉它拿完号后通知自己一下。有可能失败,人家说不定刚刚释放完呢! compareAndSetWaitStatus(pred, ws, Node.SIGNAL); } return false; }