ReentrantLock类的hasQueuedPredecessors方法和head节点的含义

部分启发来源自文章:Java并发编程--Lock

PART 1

1、如果h==t成立,h和t均为null或是同一个具体的节点,无后继节点,返回false。
2、如果h!=t成立,head.next是否为null,如果为null,返回true。什么情况下h!=t的同时h.next==null??,有其他线程第一次正在入队时,可能会出现。见AQS的enq方法,compareAndSetHead(node)完成,还没执行tail=head语句时,此时tail=null,head=newNode,head.next=null。
3、如果h!=t成立,head.next != null,则判断head.next是否是当前线程,如果是返回false,否则返回true(head节点是获取到锁的节点,但是任意时刻head节点可能占用着锁,也可能释放了锁(unlock()),未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁)

1 public final boolean hasQueuedPredecessors() {
2     Node t = tail; 
3     Node h = head;
4     Node s;
5     return h != t &&
6         ((s = h.next) == null || s.thread != Thread.currentThread());
7 }

 

PART 2  解释为什么要判断:s.thread != Thread.currentThread()

评论区3楼的提问差点让我以为我这里理解错并写错了,现在是12月,文章是4月份写的,都快忘光了...仔细再把文章和源码读了读,发现本文写的确实不够详细,有个地方还写的有点问题,漏了一些细节,因此来补充一下。  ---20191217

1、

根据ReentrantLock的解锁流程,也就是下面四个方法,可以看到当线程释放锁之后还是会在队列的head节点,但会把head的后续可唤醒节点进行唤醒(unpark)
也就是说任意时刻,head节点可能占用着锁(除了第一次执行enq()入队列时,head仅仅是个new Node(),没有实际对应任何线程,但是却“隐式”对应第一个获得锁但并未入队列的线程,和后续的head在含义上保持一致),也可能释放了锁(unlock()),未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁

1 public void unlock() {
2     sync.release(1);
3 }

 

2、

尝试释放锁,释放成功后把head.next从阻塞中唤醒

从这里以及后续的3和4可以看出,虽然线程已经释放了锁(state设置为0),但是并没有把head指向链表的下个节点(即进行类似head = head.next的操作)

这里就对应的第1点里说的,如果这里能看到,那么久可以直接看第5点了

1 public final boolean release(int arg) {
2     if (tryRelease(arg)) {
3         Node h = head;
4         if (h != null && h.waitStatus != 0)
5             unparkSuccessor(h);
6         return true;
7     }
8     return false;
9 }

 

3、

把state-1
当state=0时,把exclusiveOwnerThread设置为null,说明线程释放了锁

 1 protected final boolean tryRelease(int releases) {
 2     int c = getState() - releases;
 3     if (Thread.currentThread() != getExclusiveOwnerThread())
 4         throw new IllegalMonitorStateException();
 5     boolean free = false;
 6     if (c == 0) {
 7         free = true;
 8         setExclusiveOwnerThread(null);
 9     }
10     setState(c);
11     return free;
12 }

 

4、

把head.next指向下一个waitStatus<=0的节点,并把该节点从阻塞中唤醒

 1 private void unparkSuccessor(Node node) {
 2     int ws = node.waitStatus;
 3     if (ws < 0)
 4         compareAndSetWaitStatus(node, ws, 0);
 5 
 6     Node s = node.next;
 7     if (s == null || s.waitStatus > 0) {
 8         // 这里没看懂为什么要从tail节点倒序遍历?
 9         // 不是应该从head.next节点开始遍历更快嘛?
10         s = null;
11         for (Node t = tail; t != null && t != node; t = t.prev)
12             if (t.waitStatus <= 0)
13                 s = t;
14     }
15     if (s != null)
16         LockSupport.unpark(s.thread);
17 }

 

5、

需要提前知道一点:hasQueuedPredecessors()方法只在tryAcquire()方法里面被调用执行过,hasQueuedPredecessors()返回false表示要尝试获取锁

线程加锁的流程是:.lock() -> .acquire() -> tryAcquire()

这里我们先假设一个场景:A线程获取到了锁,然后B线程尝试去获取锁但是获取不到,此时链表的head是对用A线程,head.next对应B线程

当在B线程在第2行的tryAcquire()里面无法获取到锁时,线程B会通过下面第3行的addWaiter()方法被加入到等待链表当中,然后在第3行的acquireQueued()方法和第38行的parkAndCheckInterrupt()中park进入等待状态

在A线程释放锁之后,B线程会从38行处开始重新苏醒然后进入for(;;)循环,当B线程执行到第28行即再次执行tryAcquire()时,然后就会依次执行hasQueuedPredecessors()和s.thread != Thread.currentThread()。由前文可知,此时head仍然指向A线程,head.next也就是此处的s指向的是B线程,也同时是当前线程,所以s.thread != Thread.currentThread()为false,即此时需要尝试获取锁(再次重复这句话:未被阻塞的head.next节点对应的线程在任意时刻都是有必要去尝试获取锁)。

当此处B线程终于获得锁之后,会在第30行处把head指向B线程对应的链表结点。

 1 public final void acquire(int arg) {
 2     if (!tryAcquire(arg) &&
 3         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
 4         selfInterrupt();
 5 }
 6 
 7 protected final boolean tryAcquire(int acquires) {
 8     // 省略部分不重要的
 9     
10     if (c == 0) {
11         if (!hasQueuedPredecessors() &&
12             compareAndSetState(0, acquires)) {
13             setExclusiveOwnerThread(current);
14             return true;
15         }
16     }
17     
18     // 省略部分不重要的
19 }
20 
21 final boolean acquireQueued(final Node node, int arg) {
22     boolean failed = true;
23     try {
24         boolean interrupted = false;
25         for (;;) {
26             final Node p = node.predecessor();
27             // 这里又执行了tryAcquire
28             if (p == head && tryAcquire(arg)) {
29                 // 把head指向当前节点
30                 setHead(node);
31                 p.next = null; // help GC
32                 failed = false;
33                 return interrupted;
34             }
35             // 获取不到锁,会在此处进入线程等待状态
36             // 后续被唤醒的话,也是从这里出来,然后继续for循环
37             if (shouldParkAfterFailedAcquire(p, node) &&
38                 parkAndCheckInterrupt())
39                 interrupted = true;
40         }
41     } finally {
42         if (failed)
43             cancelAcquire(node);
44     }
45 }

 

posted @ 2019-04-05 20:43  枯木fc  阅读(4474)  评论(12编辑  收藏  举报