可重入鎖

commonBean發表於2020-10-24

可重入鎖:佔有鎖的執行緒可以重新進入.
不僅判斷鎖有沒有被鎖上,還會判斷鎖是誰鎖上的,當就是自己鎖上的時候,那麼他依舊可以再次訪問臨界資源,並把加鎖次數加一。
設計了加鎖次數,以在解鎖的時候,可以確保所有加鎖的過程都解鎖了,其他執行緒才能訪問。不然沒有加鎖的參考值,也就不知道什麼時候解鎖?解鎖多少次?才能保證本執行緒已經訪問完臨界資源了可以喚醒其他執行緒訪問了。實現相對複雜。

廣義上的可重入鎖指的是可重複可遞迴呼叫的鎖,在外層使用鎖之後,在內層仍然可以使用,並且不發生死鎖(前提得是同一個物件或者class),這樣的鎖就叫做可重入鎖。
在java 中,synchronized和java.util.concurrent.locks.ReentrantLock是可重入鎖。

可重入鎖在只要是同一執行緒的情況下,就可以重新進入,即使呼叫不同的方法, 其臨界資源也是安全的.

可重入鎖可以理解為我們常用的物件鎖的升級版

Object lock = new Object();
public void method() {
	synchronized(lock) {
		...
	}
} 

而不可重入鎖, 即使是同一執行緒,一旦某方法佔有了鎖,其他需要鎖的方法也是不能進入的.如果呼叫了其他需要佔用鎖的方法,就會造成死鎖.

可重入鎖中維護了一個private volatile int state來計數重入次數,在執行每次操作之前,判斷當前鎖持有者是否是當前物件,採用state計數,不用頻繁的持有釋放操作,這樣既提升了效率,又避免了死鎖。

在 java 併發包中有一些併發框架也使用了自旋 CAS 的方式來實現原子操作,比如 LinkedTransferQueue 類的 Xfer 方法。CAS 雖然很高效的解決原子操作,但是 CAS 仍然存在三大問題。ABA 問題,迴圈時間長開銷大和只能保證一個共享變數的原子操作。

  • 1.ABA 問題。因為 CAS 需要在操作值的時候檢查下值有沒有發生變化,如果沒有發生變化則更新,但是如果一個值原來是 A,變成了 B,又變成了 A,那麼使用 CAS 進行檢查時會發現它的值沒有發生變化,但是實際上卻變化了。ABA 問題的解決思路就是使用版本號。在變數前面追加上版本號,每次變數更新的時候把版本號加一,那麼 A-B-A 就會變成 1A-2B-3A。
    從 Java1.5 開始 JDK 的 atomic 包裡提供了一個類 AtomicStampedReference 來解決 ABA 問題。這個類的 compareAndSet 方法作用是首先檢查當前引用是否等於預期引用,並且當前標誌是否等於預期標誌,如果全部相等,則以原子方式將該引用和該標誌的值設定為給定的更新值。
  • 2.迴圈時間長開銷大。自旋 CAS 如果長時間不成功,會給 CPU 帶來非常大的執行開銷。如果 JVM 能支援處理器提供的 pause 指令那麼效率會有一定的提升,pause 指令有兩個作用,第一它可以延遲流水線執行指令(de-pipeline), 使 CPU 不會消耗過多的執行資源,延遲的時間取決於具體實現的版本,在一些處理器上延遲時間是零。第二它可以避免在退出迴圈的時候因記憶體順序衝突(memory order violation)而引起 CPU 流水線被清空(CPU pipeline flush),從而提高 CPU 的執行效率。
  • 3.只能保證一個共享變數的原子操作。當對一個共享變數執行操作時,我們可以使用迴圈 CAS 的方式來保證原子操作,但是對多個共享變數操作時,迴圈 CAS 就無法保證操作的原子性,這個時候就可以用鎖,或者有一個取巧的辦法,就是把多個共享變數合併成一個共享變數來操作。比如有兩個共享變數 i=2,j=a,合併一下 ij=2a,然後用 CAS 來操作 ij。從 Java1.5 開始 JDK 提供了 AtomicReference 類來保證引用物件之間的原子性,你可以把多個變數放在一個物件裡來進行 CAS 操作。

相關文章