引出
如果我們深入分析ReadWriteLock,會發現它有個潛在的問題:如果有線程正在讀,寫線程需要等待讀線程釋放鎖後才能獲取寫鎖,即讀的過程中不允許寫,這是一種悲觀的讀鎖。
要進一步提升併發執行效率,Java 8引入了新的讀寫鎖:StampedLock。
出現的問題:如果有999個需要讀鎖,1個需要寫鎖,此時,寫的線程,很難得到執行。
StampedLock和ReadWriteLock相比,改進之處在於:讀的過程中也允許獲取寫鎖後寫入
!這樣一來,我們讀的數據就可能不一致,所以,需要一點額外的代碼來判斷讀的過程中是否有寫入,這種讀鎖是一種樂觀鎖。
樂觀鎖的意思就是樂觀地估計讀的過程中大概率不會有寫入,因此被稱爲樂觀鎖。反過來,悲觀鎖則是讀的過程中拒絕有寫入,也就是寫入必須等待。
顯然樂觀鎖的併發效率更高,但一旦有小概率的寫入導致讀取的數據不一致,需要能檢測出來,再讀一遍就行。
舉例
public class Point {
private final StampedLock stampedLock = new StampedLock();
private double x;
private double y;
public void move(double deltaX, double deltaY) {
long stamp = stampedLock.writeLock(); // 獲取寫鎖
try {
x += deltaX;
y += deltaY;
} finally {
stampedLock.unlockWrite(stamp); // 釋放寫鎖
}
}
public double distanceFromOrigin() {
long stamp = stampedLock.tryOptimisticRead(); // 獲得一個樂觀讀鎖
// 注意下面兩行代碼不是原子操作
// 假設x,y = (100,200)
double currentX = x;
// 此處已讀取到x=100,但x,y可能被寫線程修改爲(300,400)
double currentY = y;
// 此處已讀取到y,如果沒有寫入,讀取是正確的(100,200)
// 如果有寫入,讀取是錯誤的(100,400)
if (!stampedLock.validate(stamp)) { // 檢查樂觀讀鎖後是否有其他寫鎖發生
stamp = stampedLock.readLock(); // 獲取一個悲觀讀鎖
try {
currentX = x;
currentY = y;
} finally {
stampedLock.unlockRead(stamp); // 釋放悲觀讀鎖
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
可以發現寫鎖沒有任何變化,但是讀的鎖有變化。
整個流程如下所示:
- 讀鎖先獲得一個樂觀鎖,此時並沒有加鎖
- 獲得的鎖後,正常讀取數據
- 如果此時有數據寫入,並不會因爲讀鎖而阻塞
- 此時在讀取數據操作返回前,進行檢查是否有有寫入的,如果有寫入,則升級爲讀鎖(悲觀鎖),再次讀取。
總結:
StampedLock
提供了樂觀讀鎖,可取代ReadWriteLock
以進一步提升併發性能;StampedLock
是不可重入鎖。- 至於版本號,讀鎖和讀鎖直接不會增加,
讀鎖和寫鎖之間增減128,寫鎖和寫鎖之間增加256
可重入就意味着:線程可以進入任何一個它已經擁有的鎖所同步着的代碼塊