欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

Java中的StampedLock實現(xiàn)原理詳解

 更新時間:2024年01月13日 09:59:05   作者:java架構(gòu)師-太陽  
這篇文章主要介紹了Java中的StampedLock實現(xiàn)原理詳解,ReentrantReadWriteLock采用悲觀讀,第一個讀線程拿到鎖后,第二個/第三個讀線程可以拿到鎖,特別是在讀線程很多,寫線程很少時,需要的朋友可以參考下

StampedLock(郵戳鎖/版本鎖/票據(jù)鎖)

jdk8引入 讀讀不互斥,讀寫不互斥,寫寫互斥

  • ReentrantReadWriteLock采用悲觀讀,第一個讀線程拿到鎖后,第二個/第三個讀線程可以拿到鎖,特別是在讀線程很多,寫線程很少時,寫線程可能一直拿不到鎖,在非公平鎖的情況下,導(dǎo)致寫線程餓死
  • StampedLock引入樂觀讀,讀時不加讀鎖,寫鎖也可進行寫,避免寫線程被餓死,讀數(shù)據(jù)時讀出來發(fā)現(xiàn)數(shù)據(jù)被修改了,再升級為悲觀讀,再讀一次  
  • 所有獲取鎖的方法,都返回一個郵戳(Stamp),為0表示獲取失敗,其余值都表示獲取成功
  • 所有釋放鎖的方法,都需要一個郵戳(Stamp),必須和成功獲取鎖時的一致

stamp是long類型,代表鎖的狀態(tài),當(dāng)歸0時,表示線程獲取鎖失敗,當(dāng)釋放鎖或轉(zhuǎn)換鎖時,都要傳入最初的stamp值

不可重入,如果一個線程已經(jīng)獲取寫鎖,再去獲取寫鎖的話容易死鎖

三種模式

  1. read(讀悲觀模式): 功能和ReentrantReadWriteLock的讀鎖類似,在讀時不允許有寫的操作
  2. write(悲觀寫模式): 功能和ReentrantReadWriteLock的寫鎖功能類似
  3. Optimistic(樂觀讀模式):  樂觀鎖,讀時可有寫操作,讀完后檢驗版本號是否被寫改了,若改了就再悲觀讀一次

代碼演示

class Point {
    private double x, y;
    private final StampedLock sl = new StampedLock();

    // 多個線程調(diào)用該方法,修改x和y的值
    void move(double deltaX, double deltaY) {
       long stamp = sl.writeLock();    // 獲取寫鎖
       try {
          x += deltaX;
          y += deltaY;
       } finally {
          sl.unlockWrite(stamp);       // 釋放寫鎖
       }
   }

   // 多個線程調(diào)用該方法,求距離
   double distenceFromOrigin() {

       // 樂觀讀
       long stamp = sl.tryOptimisticRead();
       // 將共享變量拷貝到線程棧, 讀:將一份數(shù)據(jù)拷貝到線程的棧內(nèi)存中

       double currentX = x, currentY = y;
       // 讀期間有其他線程修改數(shù)據(jù), 讀取后,對比讀之前的版本號和當(dāng)前的版本號,判斷數(shù)據(jù)是否可用
            // 根據(jù)stamp判斷在讀取數(shù)據(jù)和使用數(shù)據(jù)期間,有沒有其他線程修改數(shù)據(jù)
       if (!sl.validate(stamp)) {
          // 讀到的是臟數(shù)據(jù),丟棄.重新使用悲觀讀
          stamp = sl.readLock();
          try {
             currentX = x;
             currentY = y;
          } finally {
             sl.unlockRead(stamp);
          }
       }
       return Math.sqrt(currentX * currentX + currentY * currentY);
    }
}

如上代碼,有一個Point類,多個線程調(diào)用move()方法,修改坐標;還有多個線程調(diào)用distanceFromOrigin()方法,求距離 首先,執(zhí)行move操作時,要加寫鎖,和ReadWriteLock的用法沒有區(qū)別,寫操作和寫操作也是互斥的 在讀時,用樂觀讀sl.tryOptimisticRead(),相當(dāng)于在讀之前給數(shù)據(jù)的狀態(tài)做一個快照,把數(shù)據(jù)拷貝到內(nèi)存里面,在用之前,再比對一次版本號,如果版本號變了,則說明在讀的期間有其他線程修改了數(shù)據(jù),讀出來的數(shù)據(jù)廢棄,重新悲觀讀獲取數(shù)據(jù)

要說明的是,這三行關(guān)鍵代碼對順序非常敏感,不能有重排序。因為state變量已經(jīng)是volatile,所以可以禁止重排序,但stamp并不是volatile的。為此,在validate(stamp)方法里面插入內(nèi)存屏障

public boolean validate(long stamp) {
      VarHandle.acquireFence();
      return (stamp & SBITS) == (state & SBITS);
    }
public class StampedLockDemo {
    static int number = 37;
    static StampedLock stampedLock = new StampedLock();
    public void write() {
        long stamp = stampedLock.writeLock();
        System.out.println(Thread.currentThread().getName()+"\t"+"寫線程準備修改");
        try {
            number = number + 13;
        }finally {
            stampedLock.unlockWrite(stamp);
        }
        System.out.println(Thread.currentThread().getName()+"\t"+"寫線程結(jié)束修改");
    }
    //悲觀讀,讀沒有完成時候?qū)戞i無法獲得鎖
    public void read() {
        long stamp = stampedLock.readLock();
        System.out.println(Thread.currentThread().getName()+"\t"+" come in readlock code block,4 seconds continue...");
        for (int i = 0; i < 4; i++) {
            // 暫停幾秒鐘線程
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+" 正在讀取中......");
        }
        try {
            int result = number;
            System.out.println(Thread.currentThread().getName()+"\t"+" 獲得成員變量值result:"+result);
            System.out.println("寫線程沒有修改成功,讀鎖時候?qū)戞i無法介入,傳統(tǒng)的讀寫互斥");
        }finally {
            stampedLock.unlockRead(stamp);
        }
    }
    // 樂觀讀,讀的過程中也允許獲取寫鎖介入
    public void tryOptimisticRead() {
        long stamp = stampedLock.tryOptimisticRead();
        int result = number;
        // 故意間隔4秒鐘,很樂觀認為讀取中沒有其它線程修改過number值,具體靠判斷
        System.out.println("4秒前stampedLock.validate方法值(true無修改,false有修改)"+"\t"+stampedLock.validate(stamp));
        for (int i = 0; i < 4; i++) {
            try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
            System.out.println(Thread.currentThread().getName()+"\t"+"正在讀取... "+i+" 秒" +
                    "后stampedLock.validate方法值(true無修改,false有修改)"+"\t"+stampedLock.validate(stamp));
        }
        if(!stampedLock.validate(stamp)) {
            System.out.println("有人修改過------有寫操作");
            stamp = stampedLock.readLock();
            try {
                System.out.println("從樂觀讀 升級為 悲觀讀");
                result = number;
                System.out.println("重新悲觀讀后result:"+result);
            }finally {
                stampedLock.unlockRead(stamp);
            }
        }
        System.out.println(Thread.currentThread().getName()+"\t"+" finally value: "+result);
    }
    public static void main(String[] args) {
        StampedLockDemo resource = new StampedLockDemo();
        /* 傳統(tǒng)版
        new Thread(() -> {
            resource.read();
        },"readThread").start();
        //暫停幾秒鐘線程
        try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"\t"+"----come in");
            resource.write();
        },"writeThread").start();
        //暫停幾秒鐘線程
        try { TimeUnit.SECONDS.sleep(4); } catch (InterruptedException e) { e.printStackTrace(); }
        System.out.println(Thread.currentThread().getName()+"\t"+"number:" +number);*/
        new Thread(() -> {
            resource.tryOptimisticRead();
        },"readThread").start();
        // 暫停2秒鐘線程,讀過程可以寫介入,演示
        try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); }
        // 暫停6秒鐘線程
        // try { TimeUnit.SECONDS.sleep(6); } catch (InterruptedException e) { e.printStackTrace(); }
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"\t"+"----come in");
            resource.write();
        },"writeThread").start();
    }
}

樂觀讀實現(xiàn)原理

StampedLock是一個讀寫鎖,因此也會像讀寫鎖那樣,把一個state變量分成兩半,分別表示讀鎖和寫鎖的狀態(tài)。

同時,還需要一個數(shù)據(jù)的version,但是,一次CAS沒有辦法操作兩個變量,所以這個state變量本身同時也表示了數(shù)據(jù)的version。

下面先分析state變量

public class StampedLock implements java.io.Serializable {
       private static final int LG_READERS = 7;
       private static final long RUNIT = 1L;
       private static final long WBIT = 1L << LG_READERS; // 第8位表示寫鎖
       private static final long RBITS = WBIT - 1L; // 最低的7位表示讀鎖
       private static final long RFULL = RBITS - 1L; // 讀鎖的數(shù)目
       private static final long ABITS = RBITS | WBIT; // 讀鎖和寫鎖狀態(tài)合二為一
       private static final long SBITS = ~RBITS;
       private static final long ORIGIN = WBIT << 1; // state的初始值
       private transient volatile long state;
    }

用最低的8位表示讀和寫的狀態(tài),其中第8位表示寫鎖的狀態(tài),最低的7位表示讀鎖的狀態(tài)。

因為寫鎖只有一個bit位,所以寫鎖是不可重入的

初始值不為0,而是把WBIT 向左移動了一位,也就是上面的ORIGIN 常量,構(gòu)造方法如下所示

為什么state的初始值不設(shè)為0呢?

看樂觀鎖的實現(xiàn):

上面兩個方法必須結(jié)合起來看:當(dāng)state&WBIT != 0的時候,說明有線程持有寫鎖,上面的tryOptimisticRead會永遠返回0。

這樣,再調(diào)用validate(stamp),也就是validate(0)也會永遠返回false。這正是我們想要的邏輯:當(dāng)有線程持有寫鎖的時候,validate永遠返回false,無論寫線程是否釋放了寫鎖。

因為無論是否釋放了(state回到初始值)寫鎖,state值都不為0,所以validate(0)永遠為false

為什么上面的validate(…)方法不直接比較stamp=state,而要比較state&SBITS=state&SBITS 呢?

因為讀鎖和讀鎖是不互斥的! 所以,即使在“樂觀讀”的時候,state 值被修改了,但如果它改的是第7位,validate(…)還是會返回true。

另外要說明的一點是,上面使用了內(nèi)存屏障VarHandle.acquireFence();,是因為在這行代碼的下一行里面的stamp、SBITS變量不是volatile的,由此可以禁止其和前面的currentX=X,currentY=Y進行重排序 通過上面的分析,可以發(fā)現(xiàn)state的設(shè)計非常巧妙。

只通過一個變量,既實現(xiàn)了讀鎖、寫鎖的狀態(tài)記錄,還實現(xiàn)了數(shù)據(jù)的版本號的記錄

到此這篇關(guān)于Java中的StampedLock實現(xiàn)原理詳解的文章就介紹到這了,更多相關(guān)StampedLock實現(xiàn)原理內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

最新評論