1. 程式人生 > 其它 >java併發:執行緒併發控制機制之StampedLock

java併發:執行緒併發控制機制之StampedLock

StampedLock

通常情況下,使用同步鎖的程式碼如下:

synchronized(this){
    // do operation
}

Java6提供的ReentrantReadWriteLock使用方式如下:

rwlock.writeLock().lock();
try {
    // do operation
} finally {
    rwlock.writeLock().unlock();
}

ReentrantLock、ReentrantReadWriteLock和synchronized有相同的記憶體語義;相對而言,synchronized程式碼要更容易書寫,而使用ReentrantLock的程式碼必須嚴格按照一定的方式來寫,否則就會造成其他嚴重的問題。

Java8引入了一個新的讀寫鎖:StampedLock,它提供強大的樂觀讀鎖API,這意味著你能以一個較低的代價獲得一個讀鎖。

關於這裡“樂觀讀鎖”的含義請參見後文描述。

StampedLock的定義如下:

public class StampedLock implements java.io.Serializable {

Note:

在該類的定義檔案中列舉出了StampedLock的各種使用示例。

建構函式

StampedLock只提供了一個建構函式,程式碼如下:

    /**
     * Creates a new lock, initially in unlocked state.
     
*/ public StampedLock() { state = ORIGIN; }

其類圖如下:

StampedLock有三種模式(寫、悲觀讀、樂觀讀)

  • 寫鎖(writeLock)

writeLock是一個排它鎖或者獨佔鎖,某個時間只有一個執行緒可以獲取該鎖,當一個執行緒獲取該鎖後,其他請求讀鎖和寫鎖的執行緒必須等待,這類似於 ReentrantReadWriteLock 的寫鎖(不同的是這裡的寫鎖是不可重入鎖)。

請求該鎖成功後會返回一個 stamp 變數用來表示該鎖的版本,當釋放該鎖時需要呼叫 unlockWrite 方法並傳遞獲取鎖時得到的 stamp 作為引數。

  • 悲觀讀鎖(readLock)

readLock是一個共享鎖,在沒有執行緒獲寫鎖的情況下,多個執行緒可以同時獲取該鎖,這類似於 ReentrantReadWriteLock 的讀鎖 (不同的是這裡的讀鎖是不可重入鎖

請求該鎖成功後會返回一個 stamp變數用來表示該鎖的版本,當釋放該鎖時需要呼叫 unlockRead 方法並傳遞獲取鎖時得到的 stamp 作為引數。

進一步解釋這裡“悲觀”的含義:

這裡說的悲觀是指在具體操作資料前其會悲觀地認為其他執行緒可能要對當前操作資料進行修改,所以先對資料加鎖,這是在讀少寫多的情況下的一種考慮。

  • 樂觀讀鎖(tryOptimisticRead)

它是相對於悲觀鎖來說的,它在操作資料前並沒有通過 CAS 設定鎖的狀態。

如果當前沒有執行緒持有寫鎖,則簡單地返回一個非 0 的 stamp 變數用來表示該鎖的版本;在具體操作資料前需要呼叫 validate 方法驗證該 stamp 是否己經不可用。

Note:

(1)由於 tryOptimisticRead 並沒有使用 CAS 設定鎖狀態,所以不需要顯式地釋放鎖。

(2)該鎖適用於讀多寫少的場景,因為獲取讀鎖時僅使用位操作進行檢驗,不涉及 CAS 操作,所以效率會高很多;但由於沒有使用真正的鎖,在保證資料一致性上需要複製一份變數到方法棧,且在操作資料時可能其它寫執行緒己經修改了資料,所以返回的不是最新資料,但是最終一致性還是得到了保障。

使用樂觀讀鎖很容易犯錯,必須要遵循如下的使用順序

Note:

StampedLock 支援三種鎖在一定條件下進行相互轉換,例如:long tryConvertToWriteLock(long stamp) 期望把 stamp 標示的鎖升級為寫鎖。

該函式會在下面幾種情況下返回一個有效的 stamp (也就是晉升寫鎖成功):

  • 當前鎖己經是寫鎖模式
  • 當前鎖處於讀鎖模式,並且沒有其他執行緒是讀鎖模式

小結: