亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

J.U.C 簡介

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

Lock

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

Lock 簡介

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

Lock 的實現(xiàn)

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

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

Lock 的類關系圖

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

常用API

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

ReentrantLock 重入鎖

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

重入鎖的設計目的

比如調(diào)用 demo 方法獲得了當前的對象鎖,然后在這個方法中再去調(diào)用demo2,demo2 中的存在同一個實例鎖,這個時候當前線程會因為無法獲得demo2 的對象鎖而阻塞,就會產(chǎn)生死鎖。重入鎖的設計目的是避免線程的死鎖。

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ù)場景讀是多于寫的。在讀多于寫的情況下,讀寫鎖能夠提供比排它鎖更好的并發(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();
        }
    }
}

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

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

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

ReentrantLock 的實現(xiàn)原理

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

AQS 是什么

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

AQS 的兩種功能

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

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

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

Node 的組成

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

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

這里會涉及到兩個變化

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

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

這個過程也是涉及到兩個變化

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

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

ReentrantLock 的源碼分析

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

ReentrantLock 的時序圖

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

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

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

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

  • NofairSync:表示可以存在搶占鎖的功能,也就是說不管當前隊列上是否存在其他線程等待,新線程都有機會搶占鎖
  • FailSync: 表示所有線程嚴格按照 FIFO 來獲取鎖

NofairSync.lock

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

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

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

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

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

相關文章

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

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

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

    SpringBoot學習之基于注解的緩存

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

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

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

    關于RestTemplate的使用深度解析

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

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

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

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

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

    mybatis中resultMap 標簽的使用教程

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

    java中使用@Transactional會有哪些坑

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

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

    這篇文章主要介紹了SpringBoot如何啟動自動加載自定義模塊yml文件(PropertySourceFactory),具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    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應用打包為可執(zhí)行的jar或war文件,然后以通常的方式運行Spring Boot應用,本文介紹Spring Boot的Maven插件Spring Boot Maven plugin,一起看看吧
    2024-01-01

最新評論