【詳解】JUC之StampedLock

引出

如果我們深入分析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

可重入就意味着:線程可以進入任何一個它已經擁有的鎖所同步着的代碼塊

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章