StampedLock:
StampedLock
是並發包裡面JDK8版本新增的一個鎖,該鎖提供了三種模式的讀寫控制,當調用取得鎖的系列函數時,會傳回一個long 型的變量,我們稱之為戳記(stamp),這個戳記代表了鎖的狀態。其中try 系列取得鎖的函數,當取得鎖定失敗後會傳回為0的stamp值。當呼叫釋放鎖和轉換鎖的方法時需要傳入獲取鎖時傳回的stamp值。
#StampedLock提供的三種讀寫模式的鎖分別如下:
#寫鎖定witeLock: 是一個排它鎖或獨佔鎖,某時只有一個線程可以獲取該鎖,當一個線程獲取該鎖後,其他請求讀鎖和寫鎖的線程必須等待,這類似於ReentrantReadWriteLock的寫鎖(不同的是這裡的寫鎖是不可重入鎖):目前沒有執行緒持有讀鎖或寫入鎖時才可以取得到該鎖。請求該鎖定成功後會傳回一個stamp變數用來表示該鎖的版本,當釋放該鎖時需要呼叫unlockWrite方法並傳遞取得鎖定日的 stamp 參數。並且它提供了非阻塞的tryWriteLock 方法。
悲觀讀取鎖定 readLock: 是一個共享鎖定,在沒有執行緒取得獨佔寫鎖的情況下,多個執行緒可以同時取得該鎖定。如果已經有線程持有寫鎖,則其他線程請求獲取該讀鎖會被阻塞,這類似於 ReentrantReadWriteLock 的讀鎖(不同的是這裡的讀鎖是不可重入鎖)。這裡說的悲觀是指在具體操作資料前其會悲觀地認為其他執行緒可能要對自己操作的資料進行修改,所以需要先對資料加鎖,這是在讀少寫多的情況下的一種考量。請求該鎖成功後會傳回一個stamp變數用來表示該鎖的版本,當釋放該鎖時需要呼叫 unlockRead 方法並傳遞 stamp 參數。並且它提供了非阻塞的 tryReadLock 方法。
樂觀讀鎖定tryOptimisticRead: 它是相對於悲觀鎖定的,在操作資料前並沒有透過CAS設定鎖定的狀態,僅僅透過位元運算測試。如果目前沒有執行緒持有寫鎖,則簡單地傳回一個非0的stamp版本資訊。取得該stamp 後在具體操作資料前還需要呼叫validate 方法驗證該stamp 是否已經不可用,也就是看當呼叫tryOptimisticRead 返回stamp後到當前時間期間是否有其他線程持有了寫鎖,如果是則validate 會返回0否則就可以使用該stamp版本的鎖定對資料進行操作。由於tryOptimisticRead 並沒有使用CAS設定鎖定狀態,所以不需要明確釋放該鎖。此鎖的一個特點是適用於讀多寫少的場景,因為獲取讀鎖只是使用位元操作進行檢驗,不涉及CAS操作,所以效率會高很多,但是同時由於沒有使用真正的鎖,在保證數據一致性上需要複製一份要操作的變數到方法棧,並且在操作數據時可能其他寫線程已經修改了數據,而我們操作的是方法棧裡面的數據,也就是一個快照,所以最多返回的不是最新的數據,但是一致性還是有保障的。
StampedLock
也支援這三種鎖定在一定條件下進行相互轉換。例如long tryConvertToWriteLock
(long stamp)期望把stamp 標示的鎖升級為寫鎖,
這個函數會在下面幾種情況下傳回一個有效的stamp(也就是升遷寫鎖定成功):
目前鎖定已經是寫鎖定模式了。
前鎖定處於讀鎖定模式,且沒有其他執行緒是讀鎖定模式
#目前處於樂觀讀取模式,且目前寫鎖定可用
另外,StampedLock
的讀寫鎖定都是不可重入鎖定,所以在取得鎖定後釋放鎖定前不應該再呼叫會取得鎖定的動作,以避免造成呼叫線程被阻塞。當多個執行緒同時嘗試取得讀鎖和寫鎖時,誰先取得鎖沒有一定的規則,完全都是盡力而為,是隨機的。且該鎖不是直接實作 Lock 或 ReadWriteLock 接口,而是其在內部自行維護了一個雙向阻塞佇列。
下面通姑JDK8裡面提供的一個管理二維點的例子來理解以上介紹的概念。
package LockSupportTest; import com.sun.org.apache.bcel.internal.generic.BREAKPOINT; import java.util.concurrent.locks.StampedLock; public class Point_Class { private double x,y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } double distanceFromOrin() { long stamp = sl.tryOptimisticRead(); double currentX = x, currentY = y; if (!sl.validate(stamp)) { stamp = sl.readLock(); try { currentX = x; currentY = y; } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX*currentX + currentY*currentY); } void moveIfAtOrigin(double newX, double newY) { long stamp = sl.readLock(); try { while (x == 0.0 && y == 0.0) { long ws = sl.tryConvertToWriteLock(stamp); if (ws != 0L) { stamp = ws; x = newX; y = newY; break; } else { sl.unlockRead(stamp); stamp = sl.writeLock(); } } } finally { sl.unlock(stamp); } } }
如上程式碼中,Point類別裡面有兩個成員變數(x,y)用來表示一個點的二維座標,和三個操作座標變數的方法。另外實例化了一個StampedLock物件用來保證操作的原子性。
以上是Java並發程式設計如何使用StampedLock鎖定的詳細內容。更多資訊請關注PHP中文網其他相關文章!