ReentraneLock & synchronized & AQS


sychronized  (monitor監視器)  -- 自旋獲取鎖形式

  把代碼塊聲明為 synchronized,有兩個重要后果,通常是指該代碼具有 原子性(atomicity)和 可見性(visibility)。原子性意味着一個線程一次只能執行由一個指定監控對象(lock)保護的代碼,從而防止多個線程在更新共享狀態時相互沖突。可見性則更為微妙;它要對付內存緩存和編譯器優化的各種反常行為。

  monitorenter  & monitorexit

ReentranLock -- 阻塞獲取鎖形式

  Lock 框架是鎖定的一個抽象,它允許把鎖定的實現作為 Java 類,而不是作為語言的特性來實現。這就為 Lock 的多種實現留下了空間,各種實現可能有不同的調度算法、性能特性或者鎖定語義。 ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的並發性和內存語義,但是添加了類似鎖投票、定時鎖等候和可中斷鎖等候的一些特性。此外,它還提供了在激烈爭用情況下更佳的性能。(換句話說,當許多線程都想訪問共享資源時,JVM 可以花更少的時候來調度線程,把更多時間用在執行線程上。)

  reentrant 鎖意味着什么呢?簡單來說,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那么獲取計數器就加1,然后鎖需要被釋放兩次才能獲得真正釋放。這模仿了 synchronized 的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者后續) synchronized 塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個 synchronized 塊時,才釋放鎖。

 

   ReentrantLock 構造器的一個參數是 boolean 值,它允許您選擇想要一個 公平(fair)鎖,還是一個 不公平(unfair)鎖。公平鎖使線程按照請求鎖的順序依次獲得鎖;而不公平鎖則允許討價還價,在這種情況下,線程有時可以比先請求鎖的其他線程先得到鎖。在現實中,公平保證了鎖是非常健壯的鎖,有很大的性能成本。要確保公平所需要的記帳(bookkeeping)和同步,就意味着被爭奪的公平鎖要比不公平鎖的吞吐率更低。作為默認設置,應當把公平設置為 false ,除非公平對您的算法至關重要,需要嚴格按照線程排隊的順序對其進行服務。

 

在確實需要一些 synchronized 所沒有的特性的時候,比如時間鎖等候、可中斷鎖等候、無塊結構鎖、多個條件變量或者鎖投票。 ReentrantLock 還具有可伸縮性的好處,應當在高度爭用的情況下使用它,但是請記住,大多數 synchronized 塊幾乎從來沒有出現過爭用,所以可以把高度爭用放在一邊。我建議用 synchronized 開發,直到確實證明 synchronized 不合適,而不要僅僅是假設如果使用 ReentrantLock “性能會更好”。請記住,這些是供高級用戶使用的高級工具。(而且,真正的高級用戶喜歡選擇能夠找到的最簡單工具,直到他們認為簡單的工具不適用為止。)。一如既往,首先要把事情做好,然后再考慮是不是有必要做得更快。

 

 

ReentrantLock之lock形式  -- for (;;){} 循環獲取鎖 -- 自旋

  1、根據構造器創建lock,設定為公平鎖or不公平鎖

  2、lock,獲取鎖

    添加至Node隊列,包含上Node地址,下Node地址,以及線程信息

    獲取鎖隊列,從head位開始,嘗試去獲取鎖   

      獲取方式:獲取當前線程信息,得到鎖當前狀態,對處於state為0的,通過CAS操作,成功后將當前線程設置至可執行線程ExclusiveOwnerThread 。

            如果失敗,調用Thread的interrupted(),中斷線程

  3、unlock

    更改狀態,將ExclusiveOwnerThread 置為空

 

ReentrantLock擴展的功能

    實現可輪詢的鎖請求 

在內部鎖中,死鎖是致命的——唯一的恢復方法是重新啟動程序,唯一的預防方法是在構建程序時不要出錯。而可輪詢的鎖獲取模式具有更完善的錯誤恢復機制,可以規避死鎖的發生。 
如果你不能獲得所有需要的鎖,那么使用可輪詢的獲取方式使你能夠重新拿到控制權,它會釋放你已經獲得的這些鎖,然后再重新嘗試。可輪詢的鎖獲取模式,由tryLock()方法實現。此方法僅在調用時鎖為空閑狀態才獲取該鎖。如果鎖可用,則獲取鎖,並立即返回值true。如果鎖不可用,則此方法將立即返回值false。此方法的典型使用語句如下: 

  

Lock lock = ...;   
if (lock.tryLock()) {   
try {   
// manipulate protected state   
} finally {   
lock.unlock();   
}   
} else {   
// perform alternative actions   
}   

  

實現可定時的鎖請求 

   當使用內部鎖時,一旦開始請求,鎖就不能停止了,所以內部鎖給實現具有時限的活動帶來了風險。為了解決這一問題,可以使用定時鎖。當具有時限的活 

   動調用了阻塞方法,定時鎖能夠在時間預算內設定相應的超時。如果活動在期待的時間內沒能獲得結果,定時鎖能使程序提前返回。可定時的鎖獲取模式,由tryLock(long, TimeUnit)方法實現。 

  只在時間范圍內去獲取鎖,超出時間則認為無法獲取鎖。

 

 實現可中斷的鎖獲取請求 

  可中斷的鎖獲取操作允許在可取消的活動中使用。lockInterruptibly()方法能夠使你獲得鎖的時候響應中斷。

 

ReentrantLock不好與需要注意的地方

(1) lock 必須在 finally 塊中釋放。否則,如果受保護的代碼將拋出異常,鎖就有可能永遠得不到釋放!這一點區別看起來可能沒什么,但是實際上,它極為重要。忘記在 finally 塊中釋放鎖,可能會在程序中留下一個定時炸彈,當有一天炸彈爆炸時,您要花費很大力氣才有找到源頭在哪。而使用同步,JVM 將確保鎖會獲得自動釋放
(2) 當 JVM 用 synchronized 管理鎖定請求和釋放時,JVM 在生成線程轉儲時能夠包括鎖定信息。這些對調試非常有價值,因為它們能標識死鎖或者其他異常行為的來源。 Lock 類只是普通的類,JVM 不知道具體哪個線程擁有 Lock 對象。
 
 
Condition -- 條件變量
  條件(也稱為 條件隊列 或 條件變量)為線程提供了一個含義,以便在某個狀態條件現在可能為 true 的另一個線程通知它之前,一直掛起該線程(即讓其“等待”)。因為訪問此共享狀態信息發生在不同的線程中,所以它必須受保護,因此要將某種形式的鎖與該條件相關聯。等待提供一個條件的主要屬性是: 以原子方式 釋放相關的鎖,並掛起當前線程,就像  Object.wait 做的那樣。
 
  每一個 Lock可以有任意數據的 Condition對象, Condition是與 Lock綁定的,所以就有 Lock的公平性特性:如果是公平鎖,線程為按照FIFO的順序從 Condition.await中釋放,如果是非公平鎖,那么后續的鎖競爭就不保證FIFO順序了。
   await() 操作實際上就是釋放鎖,然后掛起線程,一旦條件滿足就被喚醒,再次獲取鎖;
   signal() 就是喚醒 Condition隊列中的第一個非CANCELLED節點線程;
  signalAll() 就是喚醒所有非CANCELLED節點線程。當然了遇到CANCELLED線程就需要將其從FIFO隊列中剔除。

  完整的await()操作是安裝如下步驟進行的:

    1. 將當前線程加入Condition鎖隊列。特別說明的是,這里不同於AQS的隊列,這里進入的是Condition的FIFO隊列。進行2。
    2. 釋放鎖。這里可以看到將鎖釋放了,否則別的線程就無法拿到鎖而發生死鎖。進行3。
    3. 自旋(while)掛起,直到被喚醒或者超時或者CACELLED等。進行4。
    4. 獲取鎖(acquireQueued)。並將自己從Condition的FIFO隊列中釋放,表明自己不再需要鎖(我已經拿到鎖了)

 

 

 

FIFO(固定長度隊列)

 

AQS的全稱為(AbstractQueuedSynchronizer)

  AQS是鏈表,有一個head引用來指向鏈表的頭節點,AQS在初始化的時候head、tail都是null,在運行時來回移動。此時,我們最少至少知道AQS是一個基於狀態(state)的鏈表管理方式。

  列的結構就變成了以下這種情況了,通過這樣的方式,就可以讓執行完的節點釋放掉內存區域,而不是無限制增長隊列,也就真正形成FIFO了。

 

  AQS獲取鎖是通過tryAcquire去獲取的,本身並沒有獲取方式,釋放鎖是release,通過tryRelease。

 

 

原理:

  ReentraneLock 是Lock的一種實現,也是java層次鎖的體現,而非synchronized語言特性的形式。ReentraneLock最基本的核心在於AQS,AbstractQuenedSynchronizer提供了一系列的模板方法,

通過將lock和unlock操作分別交給子類去實現,AQS提供了公平鎖和非公平鎖2種模式。而ReentraneLock是使用的Sync,是AQS的一個子類,使用的是非公平鎖。

  AQS會把請求線程放入一個CLH隊列中,由線程去嘗試獲取鎖,如果成功,則將當前運行線程設置為該線程,並將state狀態從0設置為1,再重入則會繼續加1。如果線程嘗試去獲取鎖的時候,發現已經有

線程將鎖獨占,這時AQS會將線程包裝為一個Node放入CLH隊列的tail處,然后對線程進行阻塞。在阻塞前會再次的請求獲取鎖,如果還是沒有獲取到鎖,則將線程阻塞。

 

CLH隊列的含義:CLH為隊列鎖,隊列中的Node對標注為是否需要獲取鎖。


免責聲明!

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



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