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

在上面tryAcquire方法中的第11行代码处会调用hasQueuedPredecessors方法,所以下面来看一下其实现:

1 /** 2 * ReentrantLock: 3 * 本方法是用来判断CLH队列中是否已经有不是当前线程的其他节点, 4 * 因为CLH队列都是FIFO的,head.next节点一定是等待时间最久的, 5 * 所以只需要跟它比较就行了。这里也就是在找CLH队列中是否有线程 6 * 的等待获取锁的时间比当前线程的还要长。如果有的话当前线程就 7 * 不会继续后面的加锁操作(这里再次体现“公平”的含义),没有 8 * 才会尝试加锁 9 */ 10 public final boolean hasQueuedPredecessors() { 11 Node t = tail; 12 Node h = head; 13 Node s; 14 /* 15 <1>首先判断head和tail是否不等,如果相等的话有两种情况:head和tail都为null,或者是head和tail 16 都指向那个空节点(当最后仅剩下两个节点的时候(一个空节点和一个真正等待的节点),此时再唤醒节点 17 的话,CLH队列中此时就会仅剩一个空节点了)。不管属于哪种,都代表着此时的CLH队列中没有在阻塞着的 18 节点了,那么这个时候当前线程就可以尝试加锁了; 19 <2.1>如果此时CLH队列中有节点的话,那么就判断一下head.next是否为空。我能想到的一种极端场景是: 20 假设此时CLH队列中仅有一个空节点(head和tail都指向它),就在此刻一个新的节点需要进入CLH队列里, 21 它走到了addWaiter方法中,在执行完了compareAndSetTail后,但是还没执行下面的“pred.next = node;” 22 之前,那么当前线程获取到的tail和head之间就仅有一个prev指针相连,而next指针此时还没有进行连接 23 那么此时获取到的head.next就是null了,这种情况下当前线程也不会尝试加锁,而是去CLH队列中排队 24 (这种情况下虽然h.next是null,但是是有一个等待时间比当前线程还久的节点的,只不过它的指针还没有 25 来得及连接上而已。所以当前节点会继续去排队,以此体现“公平”的含义); 26 <2.2>如果此时CLH队列中有节点,并且不属于上面第2.1条件中的特殊情况的话,还会去判断head.next 27 是否是当前线程。这个时候出现的场景就是:当前线程会在CLH队列中的head.next处,然后当前线程会再次在 28 本方法中进行判断。那么这是怎么发生的呢?一种可能的情况是:当之前持有锁的线程执行完毕释放了之后, 29 这个时候的队头节点会被唤醒,从而走到acquireQueued方法中的tryAcquire方法处,然后再走到本方法中 30 这个时候的当前线程就是被唤醒的这个线程,所以s.thread != Thread.currentThread()这个条件不成立, 31 此时当前线程就可以尝试加锁了。如果head.next不是当前线程,也就是当前线程不是等待时间最久的那个线程 32 此时就不会去加锁而是去排队去了(再次体现“公平”的含义) 33 */ 34 return h != t && 35 ((s = h.next) == null || s.thread != Thread.currentThread()); 36 } 下一篇将继续分析AQS中共享模式的实现,敬请关注。原创文章,未得准许,请勿转载,翻版必究要吐槽Doug Lea的请在下面排好队

更多内容请关注微信公众号:奇客时间

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

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