AQS源码深入分析之独占模式-ReentrantLock锁特性详解 (3)

本方法是AQS中的核心方法,需要特别注意:

1 /** 2 * AbstractQueuedSynchronizer: 3 * 注意:本方法是整个AQS的精髓所在,完成了头节点尝试获取锁资源和其他节点被阻塞的全部过程 4 */ 5 final boolean acquireQueued(final Node node, int arg) { 6 boolean failed = true; 7 try { 8 boolean interrupted = false; 9 for (; ; ) { 10 //获取当前节点的前一个节点 11 final Node p = node.predecessor(); 12 /* 13 如果前一个节点是头节点,才可以尝试获取资源,也就是实际上的CLH队列中的第一个节点 14 队列中只有第一个节点才有资格去尝试获取锁资源(FIFO),如果获取到了就不用被阻塞了 15 获取到了说明在此刻,之前的资源已经被释放了 16 */ 17 if (p == head && tryAcquire(arg)) { 18 /* 19 头指针指向当前节点,意味着该节点将变成一个空节点(头节点永远会指向一个空节点) 20 因为在上一行的tryAcquire方法已经成功的情况下,就可以释放CLH队列中的该节点了 21 */ 22 setHead(node); 23 //断开前一个节点的next指针,这样它就成为了一个孤立节点,等待被GC 24 p.next = null; 25 failed = false; 26 return interrupted; 27 } 28 /* 29 走到这里说明要么前一个节点不是head节点,要么是head节点但是尝试加锁失败。此时将队列中当前 30 节点之前的一些CANCELLED状态的节点剔除;前一个节点状态如果为SIGNAL时,就会阻塞当前线程 31 这里的parkAndCheckInterrupt阻塞操作是很有意义的。因为如果不阻塞的话,那么获取不到资源的 32 线程可能会在这个死循环里面一直运行,会一直占用CPU资源 33 */ 34 if (shouldParkAfterFailedAcquire(p, node) && 35 parkAndCheckInterrupt()) 36 //只是记录一个标志位而已,不会抛出InterruptedException异常。也就是说不会响应中断 37 interrupted = true; 38 } 39 } finally { 40 if (failed) 41 //如果tryAcquire方法中state+1溢出了,就会取消当前线程获取锁资源的请求 42 cancelAcquire(node); 43 } 44 } 45 46 /** 47 * 第22行代码处: 48 * 将node节点置为新的head节点,同时将其中的thread和prev属性置空 49 * (注意:这里并不会清空waitStatus值) 50 */ 51 private void setHead(Node node) { 52 head = node; 53 node.thread = null; 54 node.prev = null; 55 } 56 57 /** 58 * 第34行代码处: 59 */ 60 private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) { 61 int ws = pred.waitStatus; 62 if (ws == Node.SIGNAL) 63 //如果前一个节点的状态是SIGNAL,意味着当前节点可以被安全地阻塞 64 return true; 65 if (ws > 0) { 66 /* 67 从该节点往前寻找一个不是CANCELLED状态的节点(也就是处于正常阻塞状态的节点), 68 遍历过程中如果遇到了CANCELLED节点,会被剔除出CLH队列等待GC 69 */ 70 do { 71 node.prev = pred = pred.prev; 72 } while (pred.waitStatus > 0); 73 pred.next = node; 74 } else { 75 /* 76 如果前一个节点的状态是初始状态0或者是传播状态PROPAGATE时,CAS去修改其状态为SIGNAL, 77 因为当前节点最后是要被阻塞的,所以前一个节点的状态必须改为SIGNAL 78 走到这里最后会返回false,因为外面还有一个死循环,如果最后还能跳到这个方法里面的话, 79 如果之前CAS修改成功的话就会直接走进第一个if条件里面,返回true。然后当前线程被阻塞 80 CAS失败的话会再次进入到该分支中做修改 81 */ 82 compareAndSetWaitStatus(pred, ws, Node.SIGNAL); 83 } 84 return false; 85 } 86 87 /** 88 * 第35行代码处: 89 * 阻塞当前节点,后续该节点如果被unpark唤醒的时候,会从第97行代码处唤醒往下执行,返回false 90 * 可能线程在等待的时候会被中断唤醒,本方法就返回了true。这个时候该线程就会处于一种不正确的状态 91 * 返回回去后会在第37行代码处设置中断位为true,并最终返回回去。注意到下面的第110行代码处使用的是 92 * Thread.interrupted方法,也就是在返回true之后会清空中断状态,所以需要在上面的acquire方法 93 * 中、调用selfInterrupt方法里面的interrupt方法来将中断标志位重新置为true 94 */ 95 private final boolean parkAndCheckInterrupt() { 96 //当前线程会被阻塞到这行代码处,停止往下运行,等待unpark唤醒 97 LockSupport.park(this); 98 /* 99 通过上面的解释,可能会觉得下面的Thread.interrupted方法有点多余,需要清除中断标志位,最后 100 还会将中断标志位重新置为true。那么此时为什么不直接调用isInterrupted方法呢?不用清除中断标 101 志位就行了啊?其实这里使用Thread.interrupted方法是有原因的:LockSupport.park的实现会调用 102 native方法,通过查看底层的HotSpot源码中的park方法可知:如果在调用park方法时发现当前中断标 103 志位已经为true了,此时就会直接return退出本方法了(同时不会清除中断标志位),也就不会再进 104 行后续的挂起线程的操作了。也就是说,如果是中断唤醒,假如没有这里的Thread.interrupted方法 105 来清除中断标志位,那么可能下一次加锁失败还是会走进当前park方法,而此时的中断标志位仍然为 106 true。但是如上面所说,进入park方法中并不会被阻塞,也就是此时的park方法会失效,会不断在 107 acquireQueued方法中自旋,造成CPU飙高的现象出现。所以这里的Thread.interrupted方法清除中断 108 标志位是为了让后续调用的park方法能继续被成功阻塞住 109 */ 110 return Thread.interrupted(); 111 } 3.5 cancelAcquire方法

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

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