AQS : waitStatus = Propagate 的作用解析 以及讀鎖無法全獲取問題


僅供參考

Propagate 的作用:

學習AQS的過程中,發現Propagate這個狀態並沒有被顯示地使用

比如 if(ws == PROPAGATE) { 操作 }

讀了一些博客,感覺都是講的模模糊糊,於是直接看源碼。

當然,下面這篇文章也需要讀者對源碼有一定了解,本文不貼大量源碼,因為本文不是源碼解析。

 

 

 

 

 

假設現在有一種情況:

頭節點是一個獨占模式下的節點(一般這個節點的線程占有了寫鎖),后續都是共享模式下的節點,共享的節點等待獨占節點釋放資源

過了一段時間,獨占節點釋放資源。共享節點調用setHeadAndPropagate把自己變成頭節點,把剛才的獨占節點擠出了隊列。

假設setHeadAndPropagate的propagate參數大於0,也就是現在的頭節點獲取了共享資源,並且之后的節點也可以獲取共享資源。

setHeadAndPropagate中會調用doReleaseShared,因為propagate大於0。

這里的語意很清晰,其實就是當前節點獲取共享資源,如果返回的值大於0,表明下個節點的線程也能獲取共享資源,所以當前節點調用doReleaseShared來喚醒下一個節點中的線程

 

 

 接着向下看,下一個共享節點被上一個節點的doReleaseShared喚醒之后,同樣也調用setHeadAndPropagate,假設這次setHeadAndPropagate的第二個參數是0,

 也就是獲取了共享資源,但是再下一個節點是沒有資源可獲取了。也就是最后一個節點不應該被喚醒。

 

在setHeadAndPropagate方法中,會保留舊頭,也就是上圖的old,在setHeadAndPropagate中調用doReleaseShared的條件是

以下任一滿足即可

1.舊頭是空  2.舊頭的ws < 0  3.新頭是空  4.新頭的ws < 0  5.setHead的第二個參數大於0

我感覺1不太可能成立,因為old做為局部變量才剛剛被獲取,根據Java內存模型,是從內存中獲取到當前線程CPU的高速緩存中,而且函數棧幀中被引用的變量一般也不會被辣雞回收機制回收。

   2的話可能成立,因為doReleaseShared里會循環把h的ws設置成PROPAGATE狀態,如果沒有PROPAGATE,那就只能是0

   3也不太可能

   4很有可能,因為只要有后繼,后繼就會在shouldParkAfterFailedAcquire方法中把前一個節點的ws設置成SIGNAL(前提是前一個節點沒被撤銷)

   5這里假設了,等於0,所以5不成立

那么,現在,新頭無法調用doReleaseShared的條件取決於 2 和 4

     情況A : 我們假設一種情況,破除4,讓4不成立。並且我們假設PROPAGATE沒用,也就是2中直接設置成0,而不是設置成PROPAGATE。

     情況B : 我們假設一種情況,同樣不讓4成立,但是PROPAGATE保留,也就是2中可以設置成PROPAGATE。

     如果情況A會造成本應該能獲取共享資源的節點Hang住,而情況B可以讓這個節點順利獲取該獲取的資源。那么我們就證明了PROPAGATE的價值。

讓4不成立的情況:

  因為暫時無法獲取資源,新入隊的節點,ws 初始化是 0,如果后續有節點入隊,那么ws可能會被后面的節點在shouldParkAfterFailedAcquire方法中設置成SIGNAL

也就是后面的節點委托前面的節點把自己喚醒。那么我們假設在setHeadAndPropagate方法中設立一個間隙a

 

也就是試了5個條件之后,我們假設在間隙a有新的節點入隊,這時候才通過shouldParkAfterFailedAcquire把新頭Head的ws設置成SIGNAL

這時候,新頭的后面有了節點,但是間隙a之上的最后一個 h.waitStatus並不成立。(因為現在是0)。於是條件4不成立,那么還有條件2。

如果沒有PROPAGATE,那么第三個條件 h.waitStatus(這里是舊頭的ws) 也不成立,因為舊頭的ws不會被設置成PROPAGATE,而是被設置成0。所以不會調用doReleaseShared,所以不會喚醒后續結點。

這不是很正常嘛?因為tryAcquireShared的返回值是0(setHeadAndPropagte的第二個參數),表示以后的節點沒有共享資源可用了,就不應該調用doReleaseShared把后面的節點喚醒了啊

但是,共享獲取模式下,即使節點的線程沒有調用releaseShared,也是會出隊的,只要是獲取到了共享資源,那么出隊了的節點的線程可能調用releaseShared,在releaseShared中會調用

doRelaseShared。而doRelaseShared是沒有參數的,只是檢查頭節點的ws,如果頭節點的ws 是SIGNAL 那就喚醒他的后繼,並且把ws設置成0。如果ws是0,就把ws設置成PROPAGATE。

但是現在我們已經假設PROPAGATE沒有用,刪去了,於是只能檢查是不是SIGNAL,如果是就喚醒頭節點的后繼。但是如果頭節點已經喚醒了后繼了,就像我們上面的情況,頭節點的ws是0

那么調用releaseShared從而調用doReleaseShared就無事可做,而上面的五個條件檢查那里,舊頭的ws還是0,五個條件的if不成立,這種語意下,就是有不在隊列里的線程釋放了共享資源,當前節點應該喚醒后續節點,但是因為當前節點讀取到的資源數為0,所以不會喚醒后續節點。

 

 

 一種情況:

head -> A -> B

當前A線程在1處得到資源數 r  = 0,0的含義是得到了資源,但是后續節點得不到,不應該A的喚醒后續節點B

假設在1和2之間有 線程 C 調用了 releaseShare 釋放了資源(線程C是不在隊列里的,且已經獲取資源了的線程),那么理應讓 r > 0,在 setHeadAndPropagate 中去喚醒后續節點(B)

因為 r 是局部變量。所以無法辦到除了線程A之外的其他線程(比如C)改變r。但是 除A之外的其他線程(比如C)可以設置舊頭的狀態。

於是在 setHeadAndPropagate 中增加了 紅色框條件 h.waitStatus < 0 也就是判斷舊頭的狀態 來增加喚醒后續節點的情況

 

 

 

 

於是 ,隊列外的已獲取資源的線程可以通過 設置舊頭的 狀態為 PROPAGATE 來讓 喚醒繼續下去。

以上是 head 指向的還是 舊頭,新頭(當前節點)未成為 head 的情況

如果 head 指向了 新頭,因為新頭后面有節點的話,那么ws一般是 SIGNAL ,藍色框條件成立,所以可以喚醒后續。

 

 

 至於為什么 ws 不是 PROPAGATE 的時候 為什么是0 ,而不是 SIGNAL,是因為在 doReleaseShare的代碼里,會把釋放者的ws設置為0

 

也就是誰釋放了 資源,喚醒后面的節點,誰的ws就應該被設置為0

因此上文中的舊頭,ws 是 0,因為舊頭喚醒了新頭。

 

讀鎖無法完全獲取:

  假設這種情況:

一開始一個線程獲取獨占資源,后續進來了2個線程要求獲取共享資源,一個要求獨占資源,再一個要求共享資源。

    

 

 如果這時候做為頭節點的獨占資源節點釋放了獨占資源,最后一個要求獲取共享資源的節點是否能獲取共享資源呢?

 這種情況就像是依次 : 上寫鎖,上讀鎖,上讀鎖,上寫鎖,上讀鎖 ——>第一個寫鎖釋放 

 這種情況下讀鎖是否都能全部獲取到?答案是不能,只有前兩個讀鎖可以,最后一個不行,因為AQS的隊列機制,doReleaseShared釋放到第二個獨占節點的時候,發現他不是共享的

 所以就不喚醒他,最后一個共享資源節點當然也沒有辦法被喚醒,因為他要依靠前一個節點喚醒自己,而前一個節點沒醒,當然就不會喚醒自己了。

 這就是一種:只要寫鎖釋放了,其他線程要是能獲取讀鎖,那么就都能獲取讀鎖的假象。其實還是要看獲取順序的(入隊順序)

 


免責聲明!

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



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