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

ReentrantLock重入鎖底層原理示例解析

 更新時(shí)間:2023年01月08日 11:44:11   作者:一個(gè)沒有追求的技術(shù)人  
這篇文章主要為大家介紹了ReentrantLock重入鎖底層原理示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪

J.U.C 簡介

Java.util.concurrent 是在并發(fā)編程中比較常用的工具類,里面包含很多用來在并發(fā)場景中使用的組件。比如線程池、阻塞隊(duì)列、計(jì)時(shí)器、同步器、并發(fā)集合等等。并發(fā)包的作者是大名鼎鼎的 Doug Lea。

Lock

Lock 在 J.U.C 中是最核心的組件,鎖最重要的特性就是解決并發(fā)安全問題。為什么要以 Lock 作為切入點(diǎn)呢?
如果你有看過 J.U.C 包中的所有組件,一定會(huì)發(fā)現(xiàn)絕大部分的組件都有用到了 Lock。所以通過 Lock 作為切入點(diǎn)使得在后續(xù)的學(xué)習(xí)過程中會(huì)更加輕松。

Lock 簡介

在 Lock 接口出現(xiàn)之前,Java 中的應(yīng)用程序?qū)τ诙嗑€程的并發(fā)安全處理只能基于 synchronized 關(guān)鍵字來解決。但是 synchronized 在有些場景中會(huì)存在一些短板,也就是它并不適合于所有的并發(fā)場景。但是在 Java5 以后,Lock 的出現(xiàn)可以解決 synchronized 在某些場景中的短板,它比 synchronized 更加靈活。

Lock 的實(shí)現(xiàn)

Lock 本質(zhì)上是一個(gè)接口,它定義了釋放鎖和獲得鎖的抽象方法,定義成接口就意味著它定義了鎖的一個(gè)標(biāo)準(zhǔn)規(guī)范,也同時(shí)意味著鎖的不同實(shí)現(xiàn)。
實(shí)現(xiàn) Lock 接口的類有很多,以下為幾個(gè)常見的鎖實(shí)現(xiàn)

  • ReentrantLock:表示重入鎖,它是唯一一個(gè)實(shí)現(xiàn)了 Lock 接口的類。重入鎖指的是線程在獲得鎖之后,再次獲取該鎖不需要阻塞,而是直接關(guān)聯(lián)一次計(jì)數(shù)器增加重入次數(shù)
  • ReentrantReadWriteLock:重入讀寫鎖,它實(shí)現(xiàn)了 ReadWriteLock 接口,在這個(gè)類中維護(hù)了兩個(gè)鎖,一個(gè)是 ReadLock,一個(gè)是 WriteLock,他們都分別實(shí)現(xiàn)了 Lock 接口。讀寫鎖是一種適合讀多寫少的場景下解決線程安全問題的工具,基本原則是: 讀和讀不互斥、讀和寫互斥、寫和寫互斥。也就是說涉及到影響數(shù)據(jù)變化的操作都會(huì)存在互斥。
  • StampedLock: stampedLock 是 JDK8 引入的新的鎖機(jī)制,可以簡單認(rèn)為是讀寫鎖的一個(gè)改進(jìn)版本,讀寫鎖雖然通過分離讀和寫的功能使得讀和讀之間可以完全并發(fā),但是讀和寫是有沖突的,如果大量的讀線程存在,可能會(huì)引起寫線程的饑餓。stampedLock 是一種樂觀的讀策略,使得樂觀鎖完全不會(huì)阻塞寫線程

Lock 的類關(guān)系圖

Lock 有很多的鎖的實(shí)現(xiàn),但是直觀的實(shí)現(xiàn)是 ReentrantLock 重入鎖

常用API

void lock() // 如果鎖可用就獲得鎖,如果鎖不可用就阻塞直到鎖釋放
void lockInterruptibly() // 和lock()方法相似, 但阻塞的線程可中斷,拋出java.lang.InterruptedException 異常
boolean tryLock() // 非阻塞獲取鎖;嘗試獲取鎖,如果成功返回 true
boolean tryLock(long timeout, TimeUnit timeUnit) //帶有超時(shí)時(shí)間的獲取鎖方法
void unlock() // 釋放鎖

ReentrantLock 重入鎖

重入鎖,表示支持重新進(jìn)入的鎖,也就是說,如果當(dāng)前線程 t1 通過調(diào)用 lock 方法獲取了鎖之后,再次調(diào)用 lock,是不會(huì)再阻塞去獲取鎖的,直接增加重試次數(shù)就行了。synchronized 和 ReentrantLock 都是可重入鎖。那為什么鎖會(huì)存在重入的特性?假如在下面這類的場景中,存在多個(gè)加鎖的方法的相互調(diào)用,其實(shí)就是一種重入特性的場景。

重入鎖的設(shè)計(jì)目的

比如調(diào)用 demo 方法獲得了當(dāng)前的對(duì)象鎖,然后在這個(gè)方法中再去調(diào)用demo2,demo2 中的存在同一個(gè)實(shí)例鎖,這個(gè)時(shí)候當(dāng)前線程會(huì)因?yàn)闊o法獲得demo2 的對(duì)象鎖而阻塞,就會(huì)產(chǎn)生死鎖。重入鎖的設(shè)計(jì)目的是避免線程的死鎖。

public class ReentrantDemo {
    public synchronized void demo() {
        System.out.println("begin:demo");
        demo2();
    }
    public void demo2() {
        System.out.println("begin:demo1");
        synchronized (this) {
        }
    }
    public static void main(String[] args) {
        ReentrantDemo rd = new ReentrantDemo();
        new Thread(rd::demo).start();
    }
}

ReentrantLock 的使用案例

public class AtomicDemo {
    private static int count = 0;
    static Lock lock = new ReentrantLock();
    public static void inc() {
        lock.lock();
        try {
            Thread.sleep(1);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        count++;
        lock.unlock();
    }
    public static void main(String[] args) throws InterruptedException {
        for (int i = 0; i < 1000; i++) {
            new Thread(() -> {
                AtomicDemo.inc();
            }).start();
            ;
        }
        Thread.sleep(3000);
        System.out.println("result:" + count);
    }
}

ReentrantReadWriteLock

我們以前理解的鎖,基本都是排他鎖,也就是這些鎖在同一時(shí)刻只允許一個(gè)線程進(jìn)行訪問,而讀寫所在同一時(shí)刻可以允許多個(gè)線程訪問,但是在寫線程訪問時(shí),所有的讀線程和其他寫線程都會(huì)被阻塞。讀寫鎖維護(hù)了一對(duì)鎖,一個(gè)讀鎖、一個(gè)寫鎖; 一般情況下,讀寫鎖的性能都會(huì)比排它鎖好,因?yàn)榇蠖鄶?shù)場景讀是多于寫的。在讀多于寫的情況下,讀寫鎖能夠提供比排它鎖更好的并發(fā)性和吞吐量。

public class LockDemo {
    static Map<String, Object> cacheMap = new HashMap<>();
    static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    static Lock read = rwl.readLock();
    static Lock write = rwl.writeLock();
    public static final Object get(String key) {
        System.out.println("開始讀取數(shù)據(jù)");
        read.lock(); //讀鎖
        try {
            return cacheMap.get(key);
        } finally {
            read.unlock();
        }
    }
    public static final Object put(String key, Object value) {
        write.lock();
        System.out.println("開始寫數(shù)據(jù)");
        try {
            return cacheMap.put(key, value);
        } finally {
            write.unlock();
        }
    }
}

在這個(gè)案例中,通過 hashmap 來模擬了一個(gè)內(nèi)存緩存,然后使用讀寫所來保證這個(gè)內(nèi)存緩存的線程安全性。當(dāng)執(zhí)行讀操作的時(shí)候,需要獲取讀鎖,在并發(fā)訪問的時(shí)候,讀鎖不會(huì)被阻塞,因?yàn)樽x操作不會(huì)影響執(zhí)行結(jié)果。

在執(zhí)行寫操作是,線程必須要獲取寫鎖,當(dāng)已經(jīng)有線程持有寫鎖的情況下,當(dāng)前線程會(huì)被阻塞,只有當(dāng)寫鎖釋放以后,其他讀寫操作才能繼續(xù)執(zhí)行。使用讀寫鎖提升讀操作的并發(fā)性,也保證每次寫操作對(duì)所有的讀寫操作的可見性。

  • 讀鎖與讀鎖可以共享
  • 讀鎖與寫鎖不可以共享(排他)
  • 寫鎖與寫鎖不可以共享(排他)

ReentrantLock 的實(shí)現(xiàn)原理

我們知道鎖的基本原理是,基于將多線程并行任務(wù)通過某一種機(jī)制實(shí)現(xiàn)線程的串行執(zhí)行,從而達(dá)到線程安全性的目的。在 synchronized 中,我們分析了偏向鎖、輕量級(jí)鎖、樂觀鎖?;跇酚^鎖以及自旋鎖來優(yōu)化了 synchronized 的加鎖開銷,同時(shí)在重量級(jí)鎖階段,通過線程的阻塞以及喚醒來達(dá)到線程競爭和同步的目的。那么在 ReentrantLock 中,也一定會(huì)存在這樣的需要去解決的問題。就是在多線程競爭重入鎖時(shí),競爭失敗的線程是如何實(shí)現(xiàn)阻塞以及被喚醒的呢?

AQS 是什么

在 Lock 中,用到了一個(gè)同步隊(duì)列 AQS,全稱 AbstractQueuedSynchronizer,它是一個(gè)同步工具也是 Lock 用來實(shí)現(xiàn)線程同步的核心組件。如果你搞懂了 AQS,那么 J.U.C 中絕大部分的工具都能輕松掌握。

AQS 的兩種功能

從使用層面來說,AQS 的功能分為兩種:獨(dú)占和共享 獨(dú)占鎖,每次只能有一個(gè)線程持有鎖,比如前面給大家演示的 ReentrantLock 就是 以獨(dú)占方式實(shí)現(xiàn)的互斥鎖 共享鎖,允許多個(gè)線程同時(shí)獲取鎖,并發(fā)訪問共享資源,比如 ReentrantReadWriteLock

AQS 的內(nèi)部實(shí)現(xiàn)

AQS 隊(duì)列內(nèi)部維護(hù)的是一個(gè) FIFO 的雙向鏈表,這種結(jié)構(gòu)的特點(diǎn)是每個(gè)數(shù)據(jù)結(jié)構(gòu)都有兩個(gè)指針,分別指向直接的后繼節(jié)點(diǎn)和直接前驅(qū)節(jié)點(diǎn)。所以雙向鏈表可以從任意一個(gè)節(jié)點(diǎn)開始很方便的訪問前驅(qū)和后繼。每個(gè) Node 其實(shí)是由線程封裝,當(dāng)線程爭搶鎖失敗后會(huì)封裝成 Node 加入到 ASQ 隊(duì)列中去;當(dāng)獲取鎖的線程釋放鎖以后,會(huì)從隊(duì)列中喚醒一個(gè)阻塞的節(jié)點(diǎn)(線程)。

Node 的組成

釋放鎖以及添加線程對(duì)于隊(duì)列的變化

當(dāng)出現(xiàn)鎖競爭以及釋放鎖的時(shí)候,AQS 同步隊(duì)列中的節(jié)點(diǎn)會(huì)發(fā)生變化,首先看一下添加節(jié)點(diǎn)的場景。

這里會(huì)涉及到兩個(gè)變化

  • 新的線程封裝成 Node 節(jié)點(diǎn)追加到同步隊(duì)列中,設(shè)置 prev 節(jié)點(diǎn)以及修改當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)的 next 節(jié)點(diǎn)指向自己
  • 通過 CAS 講 tail 重新指向新的尾部節(jié)點(diǎn)

head 節(jié)點(diǎn)表示獲取鎖成功的節(jié)點(diǎn),當(dāng)頭結(jié)點(diǎn)在釋放同步狀態(tài)時(shí),會(huì)喚醒后繼節(jié)點(diǎn),如果后繼節(jié)點(diǎn)獲得鎖成功,會(huì)把自己設(shè)置為頭結(jié)點(diǎn),節(jié)點(diǎn)的變化過程如下

這個(gè)過程也是涉及到兩個(gè)變化

  • 修改 head 節(jié)點(diǎn)指向下一個(gè)獲得鎖的節(jié)點(diǎn)
  • 新的獲得鎖的節(jié)點(diǎn),將 prev 的指針指向 null

設(shè)置 head 節(jié)點(diǎn)不需要用 CAS,原因是設(shè)置 head 節(jié)點(diǎn)是由獲得鎖的線程來完成的,而同步鎖只能由一個(gè)線程獲得,所以不需要 CAS 保證,只需要把 head 節(jié)點(diǎn)設(shè)置為原首節(jié)點(diǎn)的后繼節(jié)點(diǎn),并且斷開原 head 節(jié)點(diǎn)的 next 引用即可

ReentrantLock 的源碼分析

以 ReentrantLock 作為切入點(diǎn),來看看在這個(gè)場景中是如何使用 AQS 來實(shí)現(xiàn)線程的同步的

ReentrantLock 的時(shí)序圖

調(diào)用 ReentrantLock 中的 lock() 方法,源碼的調(diào)用過程我使用了時(shí)序圖來展現(xiàn)。

ReentrantLock.lock() 這個(gè)是 reentrantLock 獲取鎖的入口

public void lock() {
 sync.lock();
}

sync 實(shí)際上是一個(gè)抽象的靜態(tài)內(nèi)部類,它繼承了 AQS 來實(shí)現(xiàn)重入鎖的邏輯,我們前面說過 AQS 是一個(gè)同步隊(duì)列,它能夠?qū)崿F(xiàn)線程的阻塞以及喚醒,但它并不具備業(yè)務(wù)功能,所以在不同的同步場景中,會(huì)繼承 AQS 來實(shí)現(xiàn)對(duì)應(yīng)場景的功能,Sync 有兩個(gè)具體的實(shí)現(xiàn)類,分別是:

  • NofairSync:表示可以存在搶占鎖的功能,也就是說不管當(dāng)前隊(duì)列上是否存在其他線程等待,新線程都有機(jī)會(huì)搶占鎖
  • FailSync: 表示所有線程嚴(yán)格按照 FIFO 來獲取鎖

NofairSync.lock

以非公平鎖為例,來看看 lock 中的實(shí)現(xiàn)

  • 非公平鎖和公平鎖最大的區(qū)別在于,在非公平鎖中我搶占鎖的邏輯是,不管有沒有線程排隊(duì),我先上來 cas 去搶占一下
  • CAS 成功,就表示成功獲得了鎖
  • CAS 失敗,調(diào)用 acquire(1) 走鎖競爭邏輯
final void lock() {
 if (compareAndSetState(0, 1))
   setExclusiveOwnerThread(Thread.currentThread());
 else
  acquire(1);
}

CAS 的實(shí)現(xiàn)原理

protected final boolean compareAndSetState(int expect, int update) {
 // See below for intrinsics setup to support this
 return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

通過 cas 樂觀鎖的方式來做比較并替換,這段代碼的意思是,如果當(dāng)前內(nèi)存中的 state 的值和預(yù)期值 expect 相等,則替換為 update。更新成功返回 true,否則返回 false。
這個(gè)操作是原子的,不會(huì)出現(xiàn)線程安全問題,這里面涉及到Unsafe這個(gè)類的操作,以及涉及到 state 這個(gè)屬性的意義。 state 是 AQS 中的一個(gè)屬性,它在不同的實(shí)現(xiàn)中所表達(dá)的含義不一樣,對(duì)于重入鎖的實(shí)現(xiàn)來說,表示一個(gè)同步狀態(tài)。它有兩個(gè)含義的表示

  • 當(dāng) state=0 時(shí),表示無鎖狀態(tài)
  • 當(dāng) state>0 時(shí),表示已經(jīng)有線程獲得了鎖,也就是 state=1,但是因?yàn)镽eentrantLock 允許重入,所以同一個(gè)線程多次獲得同步鎖的時(shí)候,state 會(huì)遞增,比如重入 5 次,那么 state=5。而在釋放鎖的時(shí)候,同樣需要釋放 5 次直到 state=0其他線程才有資格獲得鎖

以上就是ReentrantLock重入鎖底層原理示例解析的詳細(xì)內(nèi)容,更多關(guān)于ReentrantLock重入鎖的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • Spring實(shí)現(xiàn)文件上傳的配置詳解

    Spring實(shí)現(xiàn)文件上傳的配置詳解

    這篇文章將為大家詳細(xì)說明一下spring上傳文件如何配置,以及從request請(qǐng)求中解析到文件流的原理,文中示例代碼講解詳細(xì),感興趣的可以了解一下
    2022-08-08
  • SpringBoot學(xué)習(xí)之基于注解的緩存

    SpringBoot學(xué)習(xí)之基于注解的緩存

    spring boot對(duì)緩存支持非常靈活,我們可以使用默認(rèn)的EhCache,也可以整合第三方的框架,只需配置即可,下面這篇文章主要給大家介紹了關(guān)于SpringBoot學(xué)習(xí)之基于注解緩存的相關(guān)資料,需要的朋友可以參考下
    2022-03-03
  • SpringBoot結(jié)合Redis哨兵模式的實(shí)現(xiàn)示例

    SpringBoot結(jié)合Redis哨兵模式的實(shí)現(xiàn)示例

    這篇文章主要介紹了SpringBoot結(jié)合Redis哨兵模式的實(shí)現(xiàn)示例,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-04-04
  • 關(guān)于RestTemplate的使用深度解析

    關(guān)于RestTemplate的使用深度解析

    這篇文章主要介紹了對(duì)RestTemplate的深度解析,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-10-10
  • 使用Spring的AbstractRoutingDataSource實(shí)現(xiàn)多數(shù)據(jù)源切換示例

    使用Spring的AbstractRoutingDataSource實(shí)現(xiàn)多數(shù)據(jù)源切換示例

    這篇文章主要介紹了使用Spring的AbstractRoutingDataSource實(shí)現(xiàn)多數(shù)據(jù)源切換示例,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下。
    2017-02-02
  • java發(fā)送短信系列之限制日發(fā)送次數(shù)

    java發(fā)送短信系列之限制日發(fā)送次數(shù)

    這篇文章主要為大家詳細(xì)介紹了java發(fā)送短信系列之限制日發(fā)送次數(shù),詳細(xì)介紹了限制每日向同一個(gè)用戶(根據(jù)手機(jī)號(hào)和ip判斷)發(fā)送短信次數(shù)的方法,感興趣的小伙伴們可以參考一下
    2016-02-02
  • mybatis中resultMap 標(biāo)簽的使用教程

    mybatis中resultMap 標(biāo)簽的使用教程

    resultMap 標(biāo)簽用來描述如何從數(shù)據(jù)庫結(jié)果集中來加載對(duì)象,這篇文章重點(diǎn)給大家介紹mybatis中resultMap 標(biāo)簽的使用,感興趣的朋友一起看看吧
    2018-07-07
  • java中使用@Transactional會(huì)有哪些坑

    java中使用@Transactional會(huì)有哪些坑

    在Java中,@Transactional是一個(gè)常用的注解,用于聲明方法應(yīng)該在一個(gè)事務(wù)的上下文中執(zhí)行,本文主要介紹了java中使用@Transactional會(huì)有哪些坑,感興趣的可以了解一下
    2024-04-04
  • SpringBoot如何啟動(dòng)自動(dòng)加載自定義模塊yml文件(PropertySourceFactory)

    SpringBoot如何啟動(dòng)自動(dòng)加載自定義模塊yml文件(PropertySourceFactory)

    這篇文章主要介紹了SpringBoot如何啟動(dòng)自動(dòng)加載自定義模塊yml文件(PropertySourceFactory),具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-07-07
  • Spring Boot的Maven插件Spring Boot Maven plugin詳解

    Spring Boot的Maven插件Spring Boot Maven plu

    Spring Boot的Maven插件Spring Boot Maven plugin以Maven的方式提供Spring Boot支持,Spring Boot Maven plugin將Spring Boot應(yīng)用打包為可執(zhí)行的jar或war文件,然后以通常的方式運(yùn)行Spring Boot應(yīng)用,本文介紹Spring Boot的Maven插件Spring Boot Maven plugin,一起看看吧
    2024-01-01

最新評(píng)論