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

Java中的ReentrantLock、ReentrantReadWriteLock、StampedLock詳解

 更新時(shí)間:2024年01月10日 10:50:34   作者:苦糖果與忍冬  
這篇文章主要介紹了Java中的ReentrantLock、ReentrantReadWriteLock、StampedLock詳解,讀寫(xiě)鎖:一個(gè)資源能夠被多個(gè)讀線程訪問(wèn),或者被一個(gè)寫(xiě)線程訪問(wèn)但是不能同時(shí)存在讀寫(xiě)線程,需要的朋友可以參考下

1. ReentrantReadWriteLock

本章路線總綱 無(wú)鎖→獨(dú)占鎖→讀寫(xiě)鎖→郵戳鎖

關(guān)于鎖的大廠面試題 你知道Java里面有哪些鎖? 你說(shuō)你用過(guò)讀寫(xiě)鎖,鎖饑餓問(wèn)題是什么?有沒(méi)有比讀寫(xiě)鎖更快的鎖? StampedLock知道嗎?(郵戳鎖/票據(jù)鎖) ReentrantReadWriteLock有鎖降級(jí)機(jī)制,你知道嗎?

1.1 讀寫(xiě)鎖ReentrantReadWriteLock

讀寫(xiě)鎖:一個(gè)資源能夠被多個(gè)讀線程訪問(wèn),或者被一個(gè)寫(xiě)線程訪問(wèn)但是不能同時(shí)存在讀寫(xiě)線程。

它只允許讀讀共存,而讀寫(xiě)和寫(xiě)寫(xiě)依然是互斥的,大多實(shí)際場(chǎng)景是“讀/讀”線程間并不存在互斥關(guān)系,只有"讀/寫(xiě)"線程或"寫(xiě)/寫(xiě)"線程間的操作需要互斥的。因此引入ReentrantReadWriteLock 一個(gè)ReentrantReadWriteLock同時(shí)只能存在一個(gè)寫(xiě)鎖但是可以存在多個(gè)讀鎖,但不能同時(shí)存在寫(xiě)鎖和讀鎖(切菜還是拍蒜選一個(gè))。也即一個(gè)資源可以被多個(gè)讀操作訪問(wèn)―或一個(gè)寫(xiě)操作訪問(wèn),但兩者不能同時(shí)進(jìn)行。只有在讀多寫(xiě)少情景下,讀寫(xiě)鎖才具有較高的性能體現(xiàn)。

在這里插入圖片描述

1.2 鎖降級(jí)

在這里插入圖片描述

寫(xiě)鎖的降級(jí),降級(jí)成為了讀鎖

1)如果同一個(gè)線程持有了寫(xiě)鎖,在沒(méi)有釋放寫(xiě)鎖的情況下,它還可以繼續(xù)獲得讀鎖。這就是寫(xiě)鎖的降級(jí),降級(jí)成為了讀鎖。

2)規(guī)則慣例,先獲取寫(xiě)鎖,然后獲取讀鎖,再釋放寫(xiě)鎖的次序。

3)如果釋放了寫(xiě)鎖,那么就完全轉(zhuǎn)換為讀鎖。

在這里插入圖片描述

鎖降級(jí)是為了讓當(dāng)前線程感知到數(shù)據(jù)的變化,目的是保證數(shù)據(jù)可見(jiàn)性

如果有線程在讀,那么寫(xiě)線程是無(wú)法獲取寫(xiě)鎖的,是悲觀鎖的策略

在ReentrantReadWriteLock中,當(dāng)讀鎖被使用時(shí),如果有線程嘗試獲取寫(xiě)鎖,該寫(xiě)線程會(huì)被阻塞。所以,需要釋放所有讀鎖,才可獲取寫(xiě)鎖。

寫(xiě)鎖和讀鎖是互斥的(這里的互斥是指線程間的互斥,當(dāng)前線程可以獲取到寫(xiě)鎖又獲取到讀鎖,但是獲取到了讀鎖不能繼續(xù)獲取寫(xiě)鎖),這是因?yàn)樽x寫(xiě)鎖要保持寫(xiě)操作的可見(jiàn)性。因?yàn)?,如果允許讀鎖在被獲取的情況下對(duì)寫(xiě)鎖的獲取,那么正在運(yùn)行的其他讀線程無(wú)法感知到當(dāng)前寫(xiě)線程的操作。

ReentrantReadWriteLock讀的過(guò)程中不允許寫(xiě),只有等待線程都釋放了讀鎖,當(dāng)前線程才能獲取寫(xiě)鎖,也就是寫(xiě)入必須等待,這是一種悲觀的讀鎖,人家還在讀著那,你先別去寫(xiě),省的數(shù)據(jù)亂。

分析StampedLock(后面詳細(xì)講解),會(huì)發(fā)現(xiàn)它改進(jìn)之處在于: 讀的過(guò)程中也允許獲取寫(xiě)鎖介入(相當(dāng)牛B,讀和寫(xiě)兩個(gè)操作也讓你“共享”(注意引號(hào))),這樣會(huì)導(dǎo)致我們讀的數(shù)據(jù)就可能不一致所以,需要額外的方法來(lái)判斷讀的過(guò)程中是否有寫(xiě)入,這是一種樂(lè)觀的讀鎖。 顯然樂(lè)觀鎖的并發(fā)效率更高,但一旦有小概率的寫(xiě)入導(dǎo)致讀取的數(shù)據(jù)不一致,需要能檢測(cè)出來(lái),再讀一遍就行。

1.3 為什么要鎖降級(jí)?

在這里插入圖片描述

鎖降級(jí)確實(shí)不太貼切,明明是鎖切換,在寫(xiě)鎖釋放前由寫(xiě)鎖切換成了讀鎖。問(wèn)題的關(guān)鍵其實(shí)是為什么要在鎖切換前就加上讀鎖呢?防止釋放寫(xiě)鎖的瞬間被其他線程拿到寫(xiě)鎖然后修改了數(shù)據(jù),然后本線程在拿到讀鎖后讀取數(shù)據(jù)就發(fā)生了錯(cuò)亂。但是,我把鎖的范圍加大一點(diǎn)不就行了嗎?在寫(xiě)鎖的范圍里面完成讀鎖里面要干的事。缺點(diǎn)呢就是延長(zhǎng)了寫(xiě)鎖的占用時(shí)長(zhǎng),導(dǎo)致性能下降。對(duì)于中小公司而言沒(méi)必要,隨便在哪都能把這點(diǎn)性能撿回來(lái)了!

在這里插入圖片描述

1.4 鎖饑餓問(wèn)題

ReentrantReadWriteLock實(shí)現(xiàn)了讀寫(xiě)分離,但是一旦讀操作比較多的時(shí)候,想要獲取寫(xiě)鎖就變得比較困難了,假如當(dāng)前1000個(gè)線程,999個(gè)讀,1個(gè)寫(xiě),有可能999個(gè)讀取線程長(zhǎng)時(shí)間搶到了鎖,那1個(gè)寫(xiě)線程就悲劇了因?yàn)楫?dāng)前有可能會(huì)一直存在讀鎖,而無(wú)法獲得寫(xiě)鎖,根本沒(méi)機(jī)會(huì)寫(xiě)。

如何緩解鎖饑餓問(wèn)題? 使用"公平"策略可以一定程度上緩解這個(gè)問(wèn)題,但是"公平"策略是以犧牲系統(tǒng)吞吐量為代價(jià)的

StampedLock類的樂(lè)觀讀鎖閃亮登場(chǎng)

2. 郵戳鎖StampedLock

2.1 StampedLock橫空出世

StampedLock(也叫票據(jù)鎖)是JDK1.8中新增的一個(gè)讀寫(xiě)鎖,也是對(duì)JDK1.5中的讀寫(xiě)鎖ReentrantReadWriteLock的優(yōu)化。

stamp(戳記,long類型) 代表了鎖的狀態(tài)。當(dāng)stamp返回零時(shí),表示線程獲取鎖失敗。并且,當(dāng)釋放鎖或者轉(zhuǎn)換鎖的時(shí)候,需要傳入最初獲取的stamp值。

ReentrantReadWriteLock的讀鎖被占用的時(shí)候,其他線程嘗試獲取寫(xiě)鎖的時(shí)候會(huì)被阻塞。但是,StampedLock采取樂(lè)觀獲取鎖后,其他線程嘗試獲取寫(xiě)鎖時(shí)不會(huì)被阻塞,這其實(shí)是對(duì)讀鎖的優(yōu)化,所以,在獲取樂(lè)觀讀鎖后,還需要對(duì)結(jié)果進(jìn)行校驗(yàn)。

ReentrantReadWriteLock 允許多個(gè)線程向時(shí)讀,但是只允許一個(gè)線程寫(xiě),在線程獲取到寫(xiě)鎖的時(shí)候,其他寫(xiě)操作和讀操作都會(huì)處于阻塞狀態(tài), 讀鎖和寫(xiě)鎖也是互斥的,所以在讀的時(shí)候是不允許寫(xiě)的,讀寫(xiě)鎖比傳統(tǒng)的synchronized速度要快很多,原因就是在于ReentrantReadWriteLock支持讀并發(fā),讀讀可以共享。

對(duì)短的只讀代碼段,使用樂(lè)觀模式通??梢詼p少爭(zhēng)用并提高吞吐量

2.2 ReentrantLock、ReentrantReadWriteLock、StampedLock性能比較

public class ReentrantReadWriteLockTest {
    static Lock lock = new ReentrantLock();
    static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    static StampedLock stampedLock = new StampedLock();
    static int read = 1000;
    static int write = 3;
    static long mills = 10;
    public static void main(String[] args) {
        testReentrantLock();
        testReentrantReadWriteLock();
//        System.out.println("=========================");
        testStampedLock();
    }
    public static void testStampedLock() {
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        ExecutorService executorServiceWrite = Executors.newFixedThreadPool(3);
        CountDownLatch latch = new CountDownLatch(read + write);
        long l = System.currentTimeMillis();
        for (int i = 0; i < read; i++) {
            executorService.execute(() -> {
//                tryOptimisticRead();
                readStampedLock();
                latch.countDown();
            });
        }
        for (int i = 0; i < write; i++) {
            executorServiceWrite.execute(() -> {
                writeStampedLock();
                latch.countDown();
//                System.out.println("時(shí)間間隔:"+(System.currentTimeMillis()-l));
            });
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        executorServiceWrite.shutdown();
        System.out.println("testStampedLock執(zhí)行耗時(shí):" + (System.currentTimeMillis() - l));
    }
    public static void testReentrantLock() {
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        ExecutorService executorServiceWrite = Executors.newFixedThreadPool(3);
        CountDownLatch latch = new CountDownLatch(read + write);
        long l = System.currentTimeMillis();
        for (int i = 0; i < read; i++) {
            executorService.execute(() -> {
                read();
                latch.countDown();
            });
        }
        for (int i = 0; i < write; i++) {
            executorServiceWrite.execute(() -> {
                write();
                latch.countDown();
            });
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        executorServiceWrite.shutdown();
        System.out.println("testReentrantLock執(zhí)行耗時(shí):" + (System.currentTimeMillis() - l));
    }
    public static void testReentrantReadWriteLock() {
        ExecutorService executorService = Executors.newFixedThreadPool(100);
        ExecutorService executorServiceWrite = Executors.newFixedThreadPool(3);
        CountDownLatch latch = new CountDownLatch(read + write);
        long l = System.currentTimeMillis();
        for (int i = 0; i < read; i++) {
            executorService.execute(() -> {
                readLock();
                latch.countDown();
            });
        }
        for (int i = 0; i < write; i++) {
            executorServiceWrite.execute(() -> {
                writeLock();
                latch.countDown();
//                System.out.println("時(shí)間間隔:"+(System.currentTimeMillis()-l));
            });
        }
        try {
            latch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        executorService.shutdown();
        executorServiceWrite.shutdown();
        System.out.println("testReentrantReadWriteLock執(zhí)行耗時(shí):" + (System.currentTimeMillis() - l));
    }
    public static void tryOptimisticRead() {
        long stamp = stampedLock.tryOptimisticRead();
        try {
            Thread.sleep(mills);
            if (!stampedLock.validate(stamp)) {
                long readLock = stampedLock.readLock();
                try {
                } finally {
                    stampedLock.unlock(readLock);
                }
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
    public static void readStampedLock() {
        long stamp = stampedLock.readLock();
        try {
            Thread.sleep(mills);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            stampedLock.unlock(stamp);
        }
    }
    public static void writeStampedLock() {
        long stamp = stampedLock.writeLock();
        try {
            Thread.sleep(mills);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            stampedLock.unlock(stamp);
        }
    }
    public static void readLock() {
        readWriteLock.readLock().lock();
        try {
            Thread.sleep(mills);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readWriteLock.readLock().unlock();
        }
    }
    public static void writeLock() {
        readWriteLock.writeLock().lock();
        try {
            Thread.sleep(mills);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            readWriteLock.writeLock().unlock();
        }
    }
    public static void read() {
        lock.lock();
        try {
            Thread.sleep(mills);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public static void write() {
        lock.lock();
        try {
            Thread.sleep(mills);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

執(zhí)行結(jié)果

testReentrantLock執(zhí)行耗時(shí):15868
testReentrantReadWriteLock執(zhí)行耗時(shí):218
testStampedLock執(zhí)行耗時(shí):221

根據(jù)執(zhí)行結(jié)果可以明顯看出在讀多寫(xiě)少的情況下,ReentrantLock的性能是比較差的,而ReentrantReadWriteLock和StampedLock性能差不多相同,而StampedLock主要是為了解決ReentrantReadWriteLock可能出現(xiàn)的鎖饑餓問(wèn)題。

2.3 StampedLock總結(jié)

StampedLock的特點(diǎn) 所有獲取鎖的方法,都返回一個(gè)郵戳( Stamp) , Stamp為零表示獲取失敗,其余都表示成功; 所有釋放鎖的方法,都需要一個(gè)郵戳(Stamp),這個(gè)Stamp必須是和成功獲取鎖時(shí)得到的Stamp一致; StampedLock是不可重入的,危險(xiǎn)(如果一個(gè)線程已經(jīng)持有了寫(xiě)鎖,再去獲取寫(xiě)鎖的話就會(huì)造成死鎖)

StampedLock有三種訪問(wèn)模式 Reading (讀模式悲觀):功能和ReentrantReadWriteLock的讀鎖類似 Writing(寫(xiě)模式):功能和ReentrantRedWriteLock的寫(xiě)鎖類似 Optimistic reading(樂(lè)觀讀模式):無(wú)鎖機(jī)制,類似于數(shù)據(jù)庫(kù)中的樂(lè)觀鎖,支持讀寫(xiě)并發(fā),很樂(lè)觀認(rèn)對(duì)為讀取時(shí)沒(méi)人修改,假如被修改再實(shí)現(xiàn)升級(jí)為悲觀讀模式

主要API tryOptimisticRead():加樂(lè)觀讀鎖 validate(long stamp):校驗(yàn)樂(lè)觀讀鎖執(zhí)行過(guò)程中有無(wú)寫(xiě)鎖攪局

StampedLock的缺點(diǎn) StampedLock 不支持重入,沒(méi)有Re開(kāi)頭 StampedLock的悲觀讀鎖和寫(xiě)鎖都不支持條件變量(Condition),這個(gè)也需要注意。 使用StampedLock一定不要調(diào)用中斷操作,即不要調(diào)用interrupt()方法

有關(guān)鎖的知識(shí)都學(xué)習(xí)完了,其實(shí)挺雞肋的,工作直接用分布式鎖,幾乎不會(huì)用到JVM層級(jí)的鎖,但是面試要問(wèn),不得不學(xué)!

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

相關(guān)文章

  • 使用Java發(fā)送郵件到QQ郵箱的完整指南

    使用Java發(fā)送郵件到QQ郵箱的完整指南

    在現(xiàn)代軟件開(kāi)發(fā)中,郵件發(fā)送功能是一個(gè)常見(jiàn)的需求,無(wú)論是用戶注冊(cè)驗(yàn)證、密碼重置,還是系統(tǒng)通知,郵件都是一種重要的通信方式,本文將詳細(xì)介紹如何使用Java編寫(xiě)程序,實(shí)現(xiàn)發(fā)送郵件到QQ郵箱的功能,需要的朋友可以參考下
    2025-03-03
  • 解決idea導(dǎo)入maven項(xiàng)目缺少jar包的問(wèn)題方法

    解決idea導(dǎo)入maven項(xiàng)目缺少jar包的問(wèn)題方法

    這篇文章主要介紹了解決idea導(dǎo)入maven項(xiàng)目缺少jar包的問(wèn)題,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2020-06-06
  • SpringMVC中ModelAndView的使用及說(shuō)明

    SpringMVC中ModelAndView的使用及說(shuō)明

    這篇文章主要介紹了SpringMVC中ModelAndView的使用及說(shuō)明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-11-11
  • 淺談Spring事務(wù)傳播行為實(shí)戰(zhàn)

    淺談Spring事務(wù)傳播行為實(shí)戰(zhàn)

    這篇文章主要介紹了淺談Spring事務(wù)傳播行為實(shí)戰(zhàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09
  • Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(2)

    Java日常練習(xí)題,每天進(jìn)步一點(diǎn)點(diǎn)(2)

    下面小編就為大家?guī)?lái)一篇Java基礎(chǔ)的幾道練習(xí)題(分享)。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧,希望可以幫到你
    2021-07-07
  • Mybatis實(shí)體類對(duì)象入?yún)⒉樵兊墓P記

    Mybatis實(shí)體類對(duì)象入?yún)⒉樵兊墓P記

    這篇文章主要介紹了Mybatis實(shí)體類對(duì)象入?yún)⒉樵兊墓P記,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-06-06
  • Mybatis中通用Mapper的InsertList()用法

    Mybatis中通用Mapper的InsertList()用法

    文章介紹了通用Mapper中的insertList()方法在批量新增時(shí)的使用方式,包括自增ID和自定義ID的情況,對(duì)于自增ID,使用tk.mybatis.mapper.additional.insert.InsertListMapper包下的insertList()方法;對(duì)于自定義ID,需要重寫(xiě)insertList()方法
    2025-02-02
  • Spring源碼之請(qǐng)求路徑匹配路由方式

    Spring源碼之請(qǐng)求路徑匹配路由方式

    這篇文章主要介紹了Spring源碼之請(qǐng)求路徑匹配路由方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-09-09
  • Mybatis與微服務(wù)注冊(cè)的詳細(xì)過(guò)程

    Mybatis與微服務(wù)注冊(cè)的詳細(xì)過(guò)程

    這篇文章主要介紹了Mybatis與微服務(wù)注冊(cè),主要包括SpringBoot整合MybatisPlus,SpringBoot整合Freeamarker以及SpringBoot整合微服務(wù)&gateway&nginx的案例代碼,需要的朋友可以參考下
    2023-01-01
  • 將Java對(duì)象序列化成JSON和XML格式的實(shí)例

    將Java對(duì)象序列化成JSON和XML格式的實(shí)例

    下面小編就為大家分享一篇將Java對(duì)象序列化成JSON和XML格式的實(shí)例,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。一起跟隨小編過(guò)來(lái)看看吧
    2017-12-12

最新評(píng)論