最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

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

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

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

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

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

三種模式

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

代碼演示

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

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

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

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

       double currentX = x, currentY = y;
       // 讀期間有其他線程修改數(shù)據(jù), 讀取后,對(duì)比讀之前的版本號(hào)和當(dāng)前的版本號(hào),判斷數(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);
    }
}

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

要說明的是,這三行關(guān)鍵代碼對(duì)順序非常敏感,不能有重排序。因?yàn)閟tate變量已經(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"+"寫線程準(zhǔn)備修改");
        try {
            number = number + 13;
        }finally {
            stampedLock.unlockWrite(stamp);
        }
        System.out.println(Thread.currentThread().getName()+"\t"+"寫線程結(jié)束修改");
    }
    //悲觀讀,讀沒有完成時(shí)候?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("寫線程沒有修改成功,讀鎖時(shí)候?qū)戞i無法介入,傳統(tǒng)的讀寫互斥");
        }finally {
            stampedLock.unlockRead(stamp);
        }
    }
    // 樂觀讀,讀的過程中也允許獲取寫鎖介入
    public void tryOptimisticRead() {
        long stamp = stampedLock.tryOptimisticRead();
        int result = number;
        // 故意間隔4秒鐘,很樂觀認(rèn)為讀取中沒有其它線程修改過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("從樂觀讀 升級(jí)為 悲觀讀");
                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();
    }
}

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

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

同時(shí),還需要一個(gè)數(shù)據(jù)的version,但是,一次CAS沒有辦法操作兩個(gè)變量,所以這個(gè)state變量本身同時(shí)也表示了數(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)。

因?yàn)閷戞i只有一個(gè)bit位,所以寫鎖是不可重入的

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

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

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

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

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

因?yàn)闊o論是否釋放了(state回到初始值)寫鎖,state值都不為0,所以validate(0)永遠(yuǎn)為false

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

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

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

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

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

相關(guān)文章

  • SpringBoot集成Spring security JWT實(shí)現(xiàn)接口權(quán)限認(rèn)證

    SpringBoot集成Spring security JWT實(shí)現(xiàn)接口權(quán)限認(rèn)證

    這篇文章主要介紹了SpringBoot集成Spring security JWT實(shí)現(xiàn)接口權(quán)限認(rèn)證,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2021-04-04
  • 心動(dòng)嗎?正大光明的免費(fèi)使用IntelliJ IDEA商業(yè)版

    心動(dòng)嗎?正大光明的免費(fèi)使用IntelliJ IDEA商業(yè)版

    這篇文章主要介紹了正大光明的免費(fèi)使用IntelliJ IDEA商業(yè)版,小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧
    2020-02-02
  • springBoot跨域注解@CrossOrigin用法

    springBoot跨域注解@CrossOrigin用法

    SpringFramework 4.2GA為CORS提供了支持,Spring MVC版本必須在4.2或以上才能使用@CrossOrigin注解,如果在Controller中使用@CrossOrigin后仍遇到跨域問題,可能是因?yàn)锧RequestMapping中未指定HTTP方法,指定具體的Get或Post方法后,可以解決這一問題
    2024-10-10
  • Java中的BigDecimal使用問題小結(jié)

    Java中的BigDecimal使用問題小結(jié)

    BigDecimal 是 Java 中用于進(jìn)行精確十進(jìn)制計(jì)算的重要類, 它適用于金融、科學(xué)等需要高精度計(jì)算的場(chǎng)景,這篇文章讓我來詳細(xì)講解 Java 中的 BigDecimal 類,包括它的用途、適用場(chǎng)景、以及潛在問題,感興趣的朋友一起看看吧
    2025-04-04
  • Java C++題解leetcode 1684統(tǒng)計(jì)一致字符串的數(shù)目示例

    Java C++題解leetcode 1684統(tǒng)計(jì)一致字符串的數(shù)目示例

    這篇文章主要為大家介紹了Java C++題解leetcode 1684統(tǒng)計(jì)一致字符串的數(shù)目示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-01-01
  • 使用Springboot實(shí)現(xiàn)OAuth服務(wù)的示例詳解

    使用Springboot實(shí)現(xiàn)OAuth服務(wù)的示例詳解

    OAuth(Open Authorization)是一個(gè)開放標(biāo)準(zhǔn),用于授權(quán)第三方應(yīng)用程序訪問用戶資源,而不需要共享用戶憑證。本文主要介紹了如何使用Springboot實(shí)現(xiàn)一個(gè)OAuth服務(wù),需要的可以參考一下
    2023-05-05
  • idea常用習(xí)慣操作設(shè)置方法圖解

    idea常用習(xí)慣操作設(shè)置方法圖解

    這篇文章主要介紹了idea常用習(xí)慣操作設(shè)置方法,本文通過圖文并茂的形式給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-03-03
  • 使用SpringDataJpa創(chuàng)建中間表

    使用SpringDataJpa創(chuàng)建中間表

    這篇文章主要介紹了使用SpringDataJpa創(chuàng)建中間表,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • JUC中Future及CompletableFuture的用法與說明

    JUC中Future及CompletableFuture的用法與說明

    本文介紹了Future接口及其實(shí)現(xiàn)類FutureTask的使用方法,指出了其阻塞和CPU空轉(zhuǎn)問題,為了解決這些問題,介紹了CompletableFuture的異步任務(wù)編排,可以自動(dòng)回調(diào)異步任務(wù)結(jié)束時(shí)的方法,解決了阻塞和輪詢問題,同時(shí)提供了鏈?zhǔn)秸{(diào)用、函數(shù)式編程等等提高編程效率
    2026-04-04
  • Java服務(wù)限流算法的6種實(shí)現(xiàn)

    Java服務(wù)限流算法的6種實(shí)現(xiàn)

    服務(wù)限流是指通過控制請(qǐng)求的速率或次數(shù)來達(dá)到保護(hù)服務(wù)的目的,本文主要介紹了Java服務(wù)限流算法的6種實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下
    2023-05-05

最新評(píng)論

石狮市| 英德市| 凭祥市| 包头市| 盐津县| 海伦市| 瓮安县| 吴堡县| 永春县| 紫金县| 鄯善县| 广宗县| 佛坪县| 曲周县| 青浦区| 罗甸县| 丘北县| 中西区| 云安县| 河北区| 高碑店市| 电白县| 肥西县| 南投市| 宁河县| 岳西县| 贞丰县| 定安县| 五家渠市| 浦县| 环江| 深州市| 常山县| 遂昌县| 余姚市| 安远县| 曲阜市| 焦作市| 开封市| 德惠市| 延安市|