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 }

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM