国产 无码 综合区,色欲AV无码国产永久播放,无码天堂亚洲国产AV,国产日韩欧美女同一区二区

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

這篇具有很好參考價(jià)值的文章主要介紹了深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)。希望對(duì)大家有所幫助。如果存在錯(cuò)誤或未考慮完全的地方,請(qǐng)大家不吝賜教,您也可以點(diǎn)擊"舉報(bào)違法"按鈕提交疑問。

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

?? 嗨,您好 ?? 我是 vnjohn,在互聯(lián)網(wǎng)企業(yè)擔(dān)任 Java 開發(fā),CSDN 優(yōu)質(zhì)創(chuàng)作者
?? 推薦專欄:Spring、MySQL、Nacos、Java,后續(xù)其他專欄會(huì)持續(xù)優(yōu)化更新迭代
??文章所在專欄:JUC
?? 我當(dāng)前正在學(xué)習(xí)微服務(wù)領(lǐng)域、云原生領(lǐng)域、消息中間件等架構(gòu)、原理知識(shí)
?? 向我詢問任何您想要的東西,ID:vnjohn
??覺得博主文章寫的還 OK,能夠幫助到您的,感謝三連支持博客??
?? 代詞: vnjohn
? 有趣的事實(shí):音樂、跑步、電影、游戲

前言

介紹 ReentrantLock、AQS 之前,先分析它們的來源,來自于 JUC 中的核心組件,java.util.concurrent 在并發(fā)編程中是比較會(huì)常用的工具類,里面包含了很多在并發(fā)場(chǎng)景下使用的組件,比如:線程池 > ThreadPoolExecutor、阻塞隊(duì)列 > BlockingQueue、計(jì)數(shù)器 > CountDownLatch、循環(huán)屏障 > CyclicBarrier、信號(hào)量 > Semaphore、并發(fā)集合 > ConcurrentHashMap | CopyOnWriteArrayList |ConcurrentSkipListMap 等

ReentrantLock 與 synchronized 具有相同的基本行為、語(yǔ)義,但它擴(kuò)展了一些其他的功能且更能靈活控制鎖

1、ReentrantLock 提供了公平鎖、非公平鎖的機(jī)制,而 synchronized 并沒有公平鎖的機(jī)制
2、ReentrantLock 提供了 tryLock 方法,嘗試獲取鎖而不會(huì)阻塞線程去作其他的事情,更加靈活
3、ReentrantLock#lockInterruptibly 方法提供了響應(yīng)中斷的能力,若當(dāng)前在等待鎖的線程被中斷了,通過此方法可以捕獲到中斷異常,以便作相應(yīng)的異常處理
4、ReentrantLock > tryLock(long time, TimeUnit unit) 方法提供了鎖超時(shí)等待能力,可以指定等待鎖的超時(shí)時(shí)間,對(duì)于限時(shí)等待的場(chǎng)景很有用
5、ReentrantLock 可以通過 newCondition 方法獲取多個(gè) Condition 對(duì)象來實(shí)現(xiàn)多個(gè)條件變量,以便可以更加細(xì)粒度地調(diào)用 await、signal 等待、喚醒操作;synchronized 只能通過 wait、notify 方法實(shí)現(xiàn)簡(jiǎn)單的等待和喚醒

在該篇博文主要介紹 ReentrantLock 是如何實(shí)現(xiàn)的,以及它的核心方法源碼,如何結(jié)合 AQS 實(shí)現(xiàn)鎖解決并發(fā)安全問題的

Lock

Lock 是 JUC 組件下最核心的接口,絕大部分組件都使用到了 Lock 接口,所以先以 Lock 接口作為切入點(diǎn)講解后續(xù)的源碼

Lock 本質(zhì)上是一個(gè)接口,它提供了獲得鎖、釋放鎖、條件變量、鎖中斷能力,定義為接口就意味著它定義了一個(gè)鎖的標(biāo)準(zhǔn)規(guī)范,也同時(shí)意味著鎖的不同實(shí)現(xiàn)。實(shí)現(xiàn) Lock 接口的類有很多,以下為幾個(gè)常見的鎖實(shí)現(xiàn)

  1. ReentrantLock:表示為重入鎖,它是唯一一個(gè)實(shí)現(xiàn)了 Lock 接口的類;重入鎖是指當(dāng)前線程獲得鎖以后,再次獲取鎖不需要進(jìn)行阻塞,而是直接累加 AbstractQueuedSynchronizer#state 變量值
  2. ReentrantReadWriteLock:表示重入讀寫鎖,實(shí)現(xiàn)了 ReadWriteLock 接口,在該類中維護(hù)了兩種鎖,一個(gè)是 ReadLock,另外一個(gè)是 WriteLock,它們各自實(shí)現(xiàn)了 Lock 接口。

讀寫鎖是一種適合讀多寫少的場(chǎng)景下,來解決線程安全問題的組件,基本的原則:讀讀不互斥、讀寫互斥、寫寫互斥,一旦涉及到數(shù)據(jù)變化的操作都會(huì)是互斥的

  1. StampedLock:該鎖是 JDK 8 引入的鎖機(jī)制,是讀寫鎖的一個(gè)改進(jìn)版本,讀寫鎖雖然通過分離讀、寫功能使得讀、讀之間可以并行,但是讀、寫是互斥的,若大量的讀線程存在,可能會(huì)引起寫線程的饑餓;StampedLock 是一種樂觀鎖的讀策略,采用 CAS 樂觀鎖完全不會(huì)阻塞寫線程

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

重要的方法,簡(jiǎn)介如下:

  1. lock:若鎖可用就獲得鎖,若鎖不可用就阻塞,直接鎖被釋放
  2. lockInterruptibly:與 lock 方法相似,但阻塞的線程可中斷,會(huì)拋出 java.lang.InterruptedException 異常
  3. tryLock:非阻塞獲取鎖,嘗試獲取鎖,若成功返回 true
  4. tryLock(long timeout, TimeUnit timeUnit):帶有超時(shí)時(shí)間的獲取鎖方法
  5. unLock:釋放鎖

重入鎖

重入鎖,支持同一個(gè)線程在同一個(gè)時(shí)刻獲取同一把鎖;也就是說,若當(dāng)前線程 T1 調(diào)用 lock 方法獲取了鎖以后,再次調(diào)用 lock,是不會(huì)再以阻塞的方式去獲取鎖的,直接增加鎖的重入次數(shù)就 OK 了。

synchronized、ReentrantLock 都支持重入鎖,存在多個(gè)加鎖的方法相互調(diào)用時(shí),其實(shí)就是一種鎖可重入特性的場(chǎng)景,以下通過不同的代碼案例來演示可重入鎖是怎樣的

synchronized

/**
 * @author vnjohn
 * @since 2023/6/17
 */
public class SynchronizedDemo {
    public synchronized void lockMethodOne() {
        System.out.println("begin:lockMethodOne");
        lockMethodTwo();
    }

    public void lockMethodTwo() {
        synchronized (this) {
            System.out.println("begin:lockMethodTwo");
        }
    }

    public static void main(String[] args) {
        SynchronizedDemo synchronizedDemo = new SynchronizedDemo();
        new Thread(() -> synchronizedDemo.lockMethodOne()).start();
    }
}

調(diào)用 lockMethodOne 方法獲取了當(dāng)前實(shí)例的鎖,然后在這個(gè)方法里面還調(diào)用了 lockMethodTwo 方法,lockMethodTwo 雖然是代碼塊鎖,但鎖住的也是當(dāng)前實(shí)例;若不支持鎖可重入時(shí),當(dāng)前線程會(huì)因?yàn)闊o法獲取 lockMethodTwo 實(shí)例鎖而被阻塞,即會(huì)發(fā)生死鎖現(xiàn)象,重入鎖設(shè)計(jì)的目的是為了避免線程的死鎖

ReentrantLock

ReentrantLock 與 synchronized 同理,示例代碼如下:

/**
 * @author vnjohn
 * @since 2023/6/17
 */
public class ReentrantLockDemo {
    static Lock lock = new ReentrantLock();

    public void lockMethodOne() {
        lock.lock();
        try {
            System.out.println("begin:lockMethodOne");
            lockMethodTwo();
        } finally {
            lock.unlock();
        }
    }

    public void lockMethodTwo() {
        lock.lock();
        try {
            System.out.println("begin:lockMethodTwo");
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantLockDemo reentrantLockDemo = new ReentrantLockDemo();
        new Thread(()-> reentrantLockDemo.lockMethodOne()).start();
    }
}

ReentrantReadWriteLock 讀寫鎖

上面提及到的 synchronized、ReentrantLock 重入鎖的特性其實(shí)是排它鎖,也是悲觀鎖,該鎖在同一時(shí)刻只允許一個(gè)線程進(jìn)行訪問,而讀寫鎖在同一個(gè)時(shí)刻可以允許多個(gè)線程(讀)訪問,但是在寫線程訪問時(shí),所有的讀線程、其他寫線程都會(huì)被阻塞。讀寫鎖維護(hù)了一對(duì)鎖:讀鎖 > ReentrantReadWriteLock.ReadLock、寫鎖 > ReentrantReadWriteLock.WriteLock;一般情況下,讀寫鎖的性能會(huì)比悲觀鎖性能好,因?yàn)樵诖蠖鄶?shù)場(chǎng)景下讀都是多于寫的,讀寫鎖能夠比排它鎖提供更好的并發(fā)性、吞吐量;通過案例來演示讀寫鎖如何使用,如下:

/**
 * @author vnjohn
 * @since 2023/6/18
 */
public class ReentrantReadWriteLockDemo {
    private static Map<String, Object> CACHE_MAP = new HashMap<>();
    private static ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    private static Lock readLock = readWriteLock.readLock();
    private static Lock writeLock = readWriteLock.writeLock();

    /**
     * 通過讀鎖從本地緩存中獲取數(shù)據(jù)
     *
     * @param key
     * @return
     */
    public static Object get(String key) {
        readLock.lock();
        try {
            System.out.println("本地緩存讀取數(shù)據(jù):" + key);
            TimeUnit.SECONDS.sleep(1);
            return CACHE_MAP.get(key);
        } catch (InterruptedException e) {
            return null;
        } finally {
            readLock.unlock();
        }
    }

    /**
     * 通過寫鎖從本地緩存中獲取數(shù)據(jù)
     *
     * @param key
     * @return
     */
    public static Object put(String key, Object obj) {
        writeLock.lock();
        try {
            System.out.println("本地緩存寫入數(shù)據(jù):" + key);
            TimeUnit.SECONDS.sleep(1);
            return CACHE_MAP.put(key, obj);
        } catch (InterruptedException e) {
            return null;
        } finally {
            writeLock.unlock();
        }
    }

    public static void main(String[] args) {
        String keyOnce = "thread-batch-once";
        for (int i = 0; i < 5; i++) {
            // 演示讀寫鎖互斥的情況,
            new Thread(()-> ReentrantReadWriteLockDemo.get(keyOnce)).start();
            new Thread(()-> ReentrantReadWriteLockDemo.put(keyOnce, Thread.currentThread().getName())).start();
        }
    }

}

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

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

讀鎖、讀鎖可以共享
讀鎖、寫鎖不可以共享(排它)
寫鎖、寫鎖不可以共享(排它)

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

鎖的基本原理是,將多線程并行任務(wù)基于某一種機(jī)制實(shí)現(xiàn)線程的串行執(zhí)行,從而達(dá)到線程安全性的目的。在 synchronize 中,存在鎖升級(jí)的概念 > 偏向鎖、輕量級(jí)鎖、重量級(jí)鎖。基于 CAS 樂觀自旋鎖優(yōu)化了 synchronize 加鎖開銷,同時(shí)在重量級(jí)鎖階段,通過線程的阻塞以及喚醒來達(dá)到線程競(jìng)爭(zhēng)、同步的目的。那么在 ReentrantLock 中,也一定會(huì)存在這樣的問題需要去解決

那么在多線程競(jìng)爭(zhēng)重入鎖時(shí),競(jìng)爭(zhēng)失敗的線程是如何實(shí)現(xiàn)阻塞以及被喚醒的呢?提及這個(gè)必須先說說 AQS 是什么了!

AQS

AQS > 全稱 AbstractQueuedSynchronizer,內(nèi)部用到了一個(gè)同步等待隊(duì)列,它是一個(gè)同步工具也是 Lock 用來實(shí)現(xiàn)線程同步的核心組件

從 AQS 功能、使用層面來說,AQS 分為兩種:獨(dú)占、共享

  • 獨(dú)占鎖:同一時(shí)刻只能有一個(gè)線程持有鎖,操作、寫入資源,比如:ReentrantLock
  • 共享鎖:允許多個(gè)線程同時(shí)獲取鎖,并發(fā)訪問共享資源,比如:ReentrantReadWriteLock

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

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

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

Node 內(nèi)部結(jié)構(gòu)

static final class Node {
    /** Marker to indicate a node is waiting in shared mode */
    static final Node SHARED = new Node();
    /** Marker to indicate a node is waiting in exclusive mode */
    static final Node EXCLUSIVE = null;

    /** 線程已取消等待鎖,調(diào)用 tryLock(TimeUnit) 或 intercept 中斷方法*/
    static final int CANCELLED =  1;
    /** 表明后續(xù)線程需要被喚醒 */
    static final int SIGNAL    = -1;
    /** 表明線程在等待狀態(tài) */
    static final int CONDITION = -2;
    /**
     * 在共享模式下,該值表明下一個(gè)需要被分享的節(jié)點(diǎn)應(yīng)該無條件被分享
     */
    static final int PROPAGATE = -3;

    /**
     * 0-默認(rèn)值、CANCELLED、SIGNAL、CONDITION、PROPAGATE,
     * 后續(xù)會(huì)通過 CAS 操作改變?cè)撝禒顟B(tài)
     */
    volatile int waitStatus;

    /**
     * 前驅(qū)節(jié)點(diǎn)
     */
    volatile Node prev;

    /**
     * 后繼節(jié)點(diǎn)
     */
    volatile Node next;

    /**
     * 當(dāng)前線程
     */
    volatile Thread thread;

    /**
     * 存儲(chǔ)在 Condition 隊(duì)列中的后繼節(jié)點(diǎn)
     */
    Node nextWaiter;

    /**
     * 是否為共享模式(共享鎖)
     */
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    /**
     * 獲取前驅(qū)節(jié)點(diǎn)或拋出空指針異常
     * @return the predecessor of this node
     */
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }
	// 用于建立初始的頭或共享標(biāo)記
    Node() {    // Used to establish initial head or SHARED marker
    }
	// 該構(gòu)造方法會(huì)構(gòu)造成一個(gè) Node,添加到等待隊(duì)列中
    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
	// 該構(gòu)造方法會(huì)在 Condition 隊(duì)列中使用
    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
}

Node 變更過程

當(dāng)出現(xiàn)鎖競(jìng)爭(zhēng)或釋放鎖時(shí), AQS 同步等待隊(duì)列中的節(jié)點(diǎn)會(huì)發(fā)生變化

添加節(jié)點(diǎn)

下面來看一下添加節(jié)點(diǎn)的場(chǎng)景是怎樣的

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

在這里會(huì)發(fā)生三個(gè)變化:

  1. 新的競(jìng)爭(zhēng)鎖線程會(huì)封裝成 Node 節(jié)點(diǎn)追加到同步隊(duì)列中,設(shè)置 prev 節(jié)點(diǎn)指向原有的 tail 尾部節(jié)點(diǎn)
  2. 通過 CAS 操作將 tail 指針指向新加入的 Node 節(jié)點(diǎn)
  3. 修改原有的 tail 尾部節(jié)點(diǎn) next 指針指向新加入的 Node

以上的變化發(fā)生在核心方法:AbstractQueuedSynchronizer#addWaiter 中

釋放節(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)的變化過程如下:

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

在這里會(huì)發(fā)生兩個(gè)變化:

  1. 設(shè)置 head 頭節(jié)點(diǎn)指向下一個(gè)獲取鎖的節(jié)點(diǎn)
  2. 新的獲取鎖節(jié)點(diǎn),將 prev 指針指向 null

設(shè)置 head 頭節(jié)點(diǎn)不需要使用 CAS 操作,原因:設(shè)置 head 頭節(jié)點(diǎn)是由獲取鎖的線程來完成的,同步鎖只能由一個(gè)線程獲取,所以不適合通過 CAS 來保證,只需要把 head 頭節(jié)點(diǎn)設(shè)置為原 head 頭節(jié)點(diǎn)的后繼節(jié)點(diǎn),并且切斷原 head 頭節(jié)點(diǎn)的 next 引用即可

ReentrantLock 類源碼分析

以 ReentrantLock 類作為入口,看看該類源碼級(jí)別是如何使用 AQS 來實(shí)現(xiàn)線程同步的

時(shí)序圖

ReentrantLock#lock 方法源碼的調(diào)用過程,通過時(shí)序圖的方式來進(jìn)行展示

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

鎖競(jìng)爭(zhēng)核心方法

簡(jiǎn)單梳理了一下 lock 流程以后,下面來介紹 ReentrantLock、AQS 中一些核心方法內(nèi)容以及其作用

NonfairSync#lock

NonfairSync 實(shí)現(xiàn)類是 ReentrantLock 類內(nèi)部接口 Sync 的實(shí)現(xiàn)類,它采用非公平鎖的方式進(jìn)行鎖競(jìng)爭(zhēng), 下面來看其源碼內(nèi)容是如何實(shí)現(xiàn)的

final void lock() {
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
        acquire(1);
}

非公平鎖、公平鎖最大的區(qū)別:在于非公平鎖搶占鎖的邏輯是不管有沒有等待隊(duì)列中有沒有線程在排隊(duì),我先上來用 CAS 操作搶占一下

  • CAS 成功,即表示成功獲取到了鎖
  • CAS 失敗,調(diào)用 AbstractQueuedSynchronizer#acquire 方法走競(jìng)爭(zhēng)鎖邏輯

CAS(Compare And Set-比較并交換)

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;該操作是原子性的,不會(huì)出現(xiàn)線程安全問題,這里面會(huì)涉及到 Unsafe 類的操作

state 是 AQS 中的一個(gè)屬性,它在不同的組件實(shí)現(xiàn)中所表達(dá)的含義不一樣,對(duì)于重入鎖 ReentrantLock 來說,它有以下兩個(gè)含義:

  1. 當(dāng) state = 0 時(shí),表示無鎖狀態(tài)
  2. 當(dāng) state = 1 時(shí),表示已經(jīng)有線程獲取到了鎖

因?yàn)?ReentrantLock 允許可重入,所以同一個(gè)線程多次獲取鎖時(shí),state 會(huì)遞增,比如:在一段代碼中,當(dāng)前線程重復(fù)獲取同一把鎖三次(未釋放的情況下)state 為 3;而在釋放鎖時(shí),同樣需要釋放 3 次直至 state = 0 其他線程才有資格去獲取這把鎖

AQS#acquire

acquire 方法是 AQS 中的核心方法,若 CAS 操作未能成功,說明 state 值已經(jīng)不為 0,此時(shí)繼續(xù)調(diào)用 acquire(1) 方法操作,如下:

public final void acquire(int arg) {
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

該方法分以下幾塊邏輯進(jìn)行:

  1. 通過 tryAcquire 嘗試去獲取獨(dú)占鎖,若成功返回 true,失敗返回 false
  2. 若 tryAcquire 執(zhí)行結(jié)果為 false,則會(huì)調(diào)用 addWaiter 方法,將當(dāng)前線程封裝成 Node 添加到 AQS 隊(duì)列的尾部
  3. acquireQueued:將 Node 作為參數(shù),通過自旋的方式去嘗試獲取鎖,這里會(huì)執(zhí)行線程的阻塞等待邏輯

ReentrantLock.NonfairSync#tryAcquire

NonfairSync#tryAcquire 方法重寫至 AQS 類,AQS 該方法并沒有實(shí)現(xiàn),而是拋出異常,具體的實(shí)現(xiàn)內(nèi)容交由給子類去進(jìn)行實(shí)現(xiàn),這里采用了設(shè)計(jì)模式 > 模版方法

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

具體的子類實(shí)現(xiàn):ReentrantLock.NonfairSync#tryAcquire,該方法作用:嘗試獲取一把鎖,若成功返回 true、失敗返回 false

protected final boolean tryAcquire(int acquires) {
    return nonfairTryAcquire(acquires);
}

// Sync#nonfairTryAcquire
final boolean nonfairTryAcquire(int acquires) {
	// 獲取當(dāng)前線程
    final Thread current = Thread.currentThread();
    int c = getState();// 獲取 state 狀態(tài)
    if (c == 0) { // 代表無鎖狀態(tài)
    	// CAS 替換 state 值,CAS 成功表示鎖獲取成功
        if (compareAndSetState(0, acquires)) {
        	// 保存當(dāng)前獲取鎖的線程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 若同一個(gè)線程多次獲取同一把鎖,直接增加鎖重入次數(shù)即可
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}
  1. 獲取當(dāng)前內(nèi)存中 state 鎖狀態(tài)值
  2. state 狀態(tài)為 0 代表當(dāng)前鎖處于無鎖狀態(tài),首次獲取鎖的線程可以通過 CAS 操作更新 state 鎖狀態(tài)值
  3. 若當(dāng)前線程等于鎖占有的線程,則增加鎖重入次數(shù)即可
  4. 其他情況,代表獲取鎖失敗的線程,執(zhí)行 AQS#acquire 方法中的 addWaiter(Node.EXCLUSIVE), arg) 方法 > 添加獨(dú)占模式的 Node 隊(duì)列節(jié)點(diǎn)

AQS#addWaiter

當(dāng) tryAcquire 方法獲取鎖失敗以后,則會(huì)先調(diào)用 addWaiter 將當(dāng)前線程封裝成 Node,源碼如下:

private Node addWaiter(Node mode) {
	// 將當(dāng)前線程封裝為 Node 節(jié)點(diǎn)
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    // tail 指向 AQS 同步隊(duì)列中的尾部節(jié)點(diǎn),默認(rèn):null
    Node pred = tail;
    // tail 不為空的情況下,說明同步隊(duì)列中存在節(jié)點(diǎn)
    if (pred != null) {
    	// 將當(dāng)前線程 Node prev 前驅(qū)節(jié)點(diǎn)指針指向原來的尾部節(jié)點(diǎn)
        node.prev = pred;
        // 通過 CAS 操作將當(dāng)前 Node 設(shè)置為尾部節(jié)點(diǎn)
        if (compareAndSetTail(pred, node)) {
        	// 設(shè)置成功以后,將原來的尾部節(jié)點(diǎn) next 后繼節(jié)點(diǎn)指向當(dāng)前 Node
            pred.next = node;
            return node;
        }
    }
    // tail 為空的情況下,調(diào)用 enq 方法將當(dāng)前 Node 添加到同步隊(duì)列中
    enq(node);
    return node;
}

入?yún)ⅲ簃ode 表示節(jié)點(diǎn)的狀態(tài),ReentrantLock 傳入的狀態(tài)參數(shù):Node.EXCLUSIVE 代表獨(dú)占模式,意味著重入鎖獲取鎖采用獨(dú)占的方式,addWaiter 方法基本的執(zhí)行過程,如下所示:

  1. 將當(dāng)前線程封裝為 Node 節(jié)點(diǎn)對(duì)象
  2. 判斷當(dāng)前同步隊(duì)列中的尾部節(jié)點(diǎn)是否為空
  3. 若尾部節(jié)點(diǎn)不為空,通過 CAS 操作將當(dāng)前線程的 Node 添加到同步隊(duì)列中,并將新加入的 Node 設(shè)置為尾節(jié)點(diǎn),采用尾插法的方式進(jìn)行隊(duì)列入隊(duì)的
  4. 若尾部節(jié)點(diǎn)為空或者 CAS 設(shè)置尾部節(jié)點(diǎn)失敗,調(diào)用 enq 方法將當(dāng)前 Node 添加到同步隊(duì)列中

AQS#enq

該方法通過自旋的方式以便可以成功將當(dāng)前節(jié)點(diǎn)加入到同步隊(duì)列中

/**
 *      +------+  prev +-----+       +-----+
 * head |      | <---- |     | <---- |     |  tail
 *      +------+       +-----+       +-----+
 */
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        // 尾節(jié)點(diǎn)為空,說明當(dāng)前同步隊(duì)列中未存在元素
        // 初始化一個(gè)空對(duì)象 Node,先通過 CAS 將其設(shè)置為頭節(jié)點(diǎn),若成功再將其設(shè)置為尾節(jié)點(diǎn)
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
        	// 當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)指向原來尾部節(jié)點(diǎn)、將當(dāng)前節(jié)點(diǎn)設(shè)置為尾部節(jié)點(diǎn)、原來尾部節(jié)點(diǎn)后繼節(jié)點(diǎn)指向當(dāng)前節(jié)點(diǎn)
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}

enq 方法執(zhí)行只是為了維護(hù)同步等待隊(duì)列的節(jié)點(diǎn)元素,當(dāng)多個(gè)線程開始競(jìng)爭(zhēng)鎖時(shí),必然會(huì)進(jìn)行排隊(duì),第一次入隊(duì)的線程不僅要承擔(dān)將自身加入到隊(duì)列中,同時(shí)還需要初始化一個(gè)空 Node 對(duì)象,將其設(shè)置為頭尾節(jié)點(diǎn)

圖解分析
假設(shè)有 3 個(gè)線程同時(shí)來爭(zhēng)搶鎖,那么截止到 AQS#addWaiter 或 AQS#enq 方法結(jié)束之后,AQS 中同步等待隊(duì)列結(jié)構(gòu)圖,如下所示:

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

AQS#acquireQueued

當(dāng)執(zhí)行完 AQS#addWaiter 方法以后,會(huì)將返回的 Node 參數(shù)傳遞給 acquireQueued 方法,去實(shí)現(xiàn)鎖競(jìng)爭(zhēng)、阻塞線程邏輯,方法源碼如下:

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
        	// 獲取當(dāng)前 Node 節(jié)點(diǎn)的前驅(qū) prev 節(jié)點(diǎn)
            final Node p = node.predecessor();
            // 若前驅(qū) prev 節(jié)點(diǎn)為頭節(jié)點(diǎn),當(dāng)前 Node 重新嘗試獲取鎖
            if (p == head && tryAcquire(arg)) {
            	// 獲取鎖成功,說明鎖已經(jīng)被持有的線程所釋放,設(shè)置當(dāng)前 Node 為頭節(jié)點(diǎn)
                setHead(node);
                // 將原 head 頭節(jié)點(diǎn)從同步隊(duì)列中移除
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            // 獲取鎖失敗,會(huì)獲取前驅(qū)節(jié)點(diǎn)并更新 waitStatus 狀態(tài)值
            // 隨機(jī)調(diào)用原生鎖 LockSupport#park 方法阻塞當(dāng)前競(jìng)爭(zhēng)鎖的線程
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

acquireQueued 方法基本的執(zhí)行過程同時(shí)以 AQS#enq 分析中的圖解分析為例,如下所示:

  1. 獲取當(dāng)前節(jié)點(diǎn) Node prev 前驅(qū)節(jié)點(diǎn)

比如:當(dāng)前是線程 B,那么它的前驅(qū)節(jié)點(diǎn)就是 Thread 為 null 的頭節(jié)點(diǎn)

  1. 若 prev 前驅(qū)節(jié)點(diǎn)為 head 頭節(jié)點(diǎn),那么它有資格再去爭(zhēng)搶一次鎖,調(diào)用 ReentrantLock.NonfairSync#tryAcquire 方法搶占鎖

也就是說線程 B 在這里也會(huì)有一次機(jī)會(huì)再去爭(zhēng)奪鎖

  1. 若搶占鎖成功,把當(dāng)前搶到鎖的 Node 節(jié)點(diǎn)設(shè)置為 head 頭節(jié)點(diǎn),并且移除原有的頭節(jié)點(diǎn)
  2. 若搶占鎖失敗,先通過 shouldParkAfterFailedAcquire 方法更新一次 waitStatus 值狀態(tài),然后再調(diào)用原生鎖支持 > LockSupport.park(this) 阻塞當(dāng)前線程等待后續(xù)被喚醒

仍然以線程 A 未釋放鎖,線程 B 處于首節(jié)點(diǎn)的情況作以說明:由于 acquireQueued 方法是死循環(huán),所有的 Node 新建時(shí) waitStatus 屬性值都為 0 (除了 Condition 條件變量)第一次遍歷時(shí)會(huì)搶一次鎖;這一次會(huì)調(diào)用 shouldParkAfterFailedAcquire 方法將 waitStatus -> 0 更改為 SIGNAL-待喚醒狀態(tài),該方法執(zhí)行完以后會(huì)返回 false,然后會(huì)繼續(xù)第二次循環(huán),第二次執(zhí)行 shouldParkAfterFailedAcquire 方法返回 true,接著會(huì)調(diào)用 parkAndCheckInterrupt 方法使用原生鎖方式:LockSupport.park(this) > 阻塞當(dāng)前線程并返回當(dāng)前線程是否中斷的標(biāo)識(shí)

  1. 最后,通過 cancelAcquire 方法取消當(dāng)前線程獲取鎖的節(jié)點(diǎn)

AQS#shouldParkAfterFailedAcquire

線程 A 未釋放鎖,線程 B、線程 C 來爭(zhēng)搶鎖肯定會(huì)失敗,失敗以后會(huì)調(diào)用 shouldParkAfterFailedAcquire 方法,Node#waitStatus 存在五種狀態(tài),如下:

  1. CANCELLED:值為 1,即為結(jié)束狀態(tài),在同步等待隊(duì)列中等待的線程超時(shí)或被中斷,需要從同步隊(duì)列中取消該線程 Node 節(jié)點(diǎn),進(jìn)入該狀態(tài)以后的節(jié)點(diǎn)將不再發(fā)生變化
  2. 0:初始化狀態(tài)
  3. SIGNAL:值為 -1,當(dāng)前驅(qū)節(jié)點(diǎn)釋放鎖以后,就會(huì)通知標(biāo)識(shí)為 SIGNAL 狀態(tài)的后繼 Node 節(jié)點(diǎn)線程
  4. CONDITION:值為 -2,與 ReentrantLock#newCondition 條件變量有關(guān)系,AQS.ConditionObject#addConditionWaiter 在該方法中會(huì)提現(xiàn)出來
  5. PROPAGATE:值為 -3,在共享模式下,PROPAGATE 處于可運(yùn)行狀態(tài)
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
   int ws = pred.waitStatus;
   // 若前驅(qū)節(jié)點(diǎn)為 SIGNAL,意味著只需要等待其他前驅(qū)節(jié)點(diǎn)的線程被釋放
   // 當(dāng)獲取鎖的線程調(diào)用 release 方法后,該前驅(qū)節(jié)點(diǎn)的線程就會(huì)被喚醒
   if (ws == Node.SIGNAL)
       // 返回 true,意味著當(dāng)前線程可以放心調(diào)用 parkAndCheckInterrupt 方法進(jìn)行掛起
       return true;
   // waitState 大于 0,意味著 prev 前驅(qū)節(jié)點(diǎn)取消了排隊(duì)操作,直接將這個(gè)節(jié)點(diǎn)移除即可
   if (ws > 0) {
       // 相當(dāng)于:pred=pred.prev;node.prev=pred;
       // 從尾部節(jié)點(diǎn)開始查找,直到將所有的 CANCELLED 節(jié)點(diǎn)移除
       do {
           node.prev = pred = pred.prev;
       } while (pred.waitStatus > 0);
       pred.next = node;
   } else {
       // 使用 CAS 設(shè)置前驅(qū) prev 節(jié)點(diǎn)狀態(tài)為 SIGNAL
       compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
   }
   return false;
}

該方法主要作用:通過 Node 節(jié)點(diǎn)狀態(tài)來判斷,線程 B、線程 C 競(jìng)爭(zhēng)鎖失敗后是否應(yīng)該要被掛起

  1. 若 Thread-B、Thread-C 前驅(qū) prev 節(jié)點(diǎn)狀態(tài)為 SIGNAL,表示可以放心掛起所在的當(dāng)前線程
  2. 若當(dāng)前線程 prev 節(jié)點(diǎn)狀態(tài)為 CANCELLED,采用循環(huán)方式掃描同步等待隊(duì)列將 CANCELLED 狀態(tài)的節(jié)點(diǎn)從同步等待隊(duì)列中移除
  3. 以上兩個(gè)條件都滿足,將前驅(qū) prev 節(jié)點(diǎn)狀態(tài)改為 SIGNAL,返回 false

該方法返回 true、false 代表的含義不同,當(dāng)返回 true 時(shí),不會(huì)進(jìn)入 AQS#acquireQueued 方法的下一次循環(huán),會(huì)調(diào)用 parkAndCheckInterrupt 方法將當(dāng)前線程阻塞;當(dāng)返回 false 時(shí),會(huì)進(jìn)入到 AQS#acquireQueued 方法的下一次循環(huán)再次嘗試爭(zhēng)搶一次鎖,當(dāng)搶鎖成功當(dāng)前線程就是獨(dú)占線程,搶鎖失敗再調(diào)用 parkAndCheckInterrupt 方法將當(dāng)前線程阻塞

AQS#parkAndCheckInterrupt

parkAndCheckInterrupt 方法邏輯比較簡(jiǎn)單,先看源碼,如下:

private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}
  1. 調(diào)用 LockSupport#park 方法掛起當(dāng)前線程編程 waiting 狀態(tài)

LockSupport 原生鎖支持
park 方法:阻塞當(dāng)前線程,不需要使用 sync 修飾,直接可以使用
unpark 方法:?jiǎn)拘阎付ň€程
unpark 方法可以先于 park 方法先調(diào)用,unpark 相當(dāng)于是獲取許可數(shù)量 1、park 相當(dāng)于是消費(fèi)許可數(shù)量 1

  1. Thread#interrupted:返回當(dāng)前線程是否被其他線程觸發(fā)過中斷請(qǐng)求,也就是調(diào)用 Thread#interrupt 方法;若有觸發(fā)過中斷請(qǐng)求,那么該方法會(huì)返回當(dāng)前的中斷標(biāo)識(shí)為 true,并且會(huì)對(duì)中斷標(biāo)識(shí)進(jìn)行復(fù)位標(biāo)識(shí)已經(jīng)響應(yīng)過了中斷請(qǐng)求,也就是會(huì)在 AQS#acquire 方法中執(zhí)行 selfInterrupt 方法
  2. selfInterrupt:標(biāo)識(shí)當(dāng)前線程是否執(zhí)行 AQS#acquireQueued 方法時(shí)被中斷過,若被中斷過,則需要響應(yīng)中斷請(qǐng)求,因?yàn)樵诰€程調(diào)用 AQS#acquireQueued 方法是不會(huì)去響應(yīng)中斷請(qǐng)求的

通過 AQS#acquireQueued 方法來競(jìng)爭(zhēng)鎖,若 Thread-A 仍然還在執(zhí)行中未釋放鎖,那么 Thread-B、Thread-C 還會(huì)繼續(xù)掛起

到這里,鎖相關(guān)的競(jìng)爭(zhēng)方法在這里基本上都介紹過了,其實(shí)看到這里,能發(fā)現(xiàn),當(dāng)競(jìng)爭(zhēng)的鎖線程失敗時(shí),會(huì)調(diào)用 LockSupport#park 方法阻塞住,等待鎖匙放時(shí),還會(huì)有 LockSuppor#unpark 方法進(jìn)行鎖匙放,下面就來分析鎖匙放時(shí)的一些核心方法是如何處理的!?。?/p>

鎖釋放核心方法

若此時(shí) Thread-A 釋放鎖了,那么接下來 Thread-B、Thread-C 是如何走的呢?

ReentrantLock#unlock

public void unlock() {
    sync.release(1);
}

在 unlock 方法中,會(huì)調(diào)用其內(nèi)部類 Sync#release 方法,但由于 Sync 并未其父類 AQS#release 方法,所以它會(huì)延用其父類 AQS#release 方法的處理邏輯,源碼如下:

public final boolean release(int arg) {
	// 若釋放占用當(dāng)前鎖的節(jié)點(diǎn) Node 線程成功
    if (tryRelease(arg)) {
        // 獲取 AQS 同步等待隊(duì)列中的 head 頭節(jié)點(diǎn)
        Node h = head;
        // 若 head 節(jié)點(diǎn)不為空 & waitStatus 非默認(rèn)值,直接喚醒下一個(gè)節(jié)點(diǎn)去爭(zhēng)搶鎖
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

該方法主要的執(zhí)行流程分為幾步,如下:

  1. 先調(diào)用 ReentrantLock.Sync#tryRelease 方法探測(cè)鎖釋放是否可以成功,它來自 AQS 子類 ReentrantLock.Sync 所實(shí)現(xiàn)的
  2. 獲取同步等待隊(duì)列中的 head 首節(jié)點(diǎn),若其不為空,并且它的 waitStatus 屬性值非默認(rèn)值 0,那么就會(huì)調(diào)用 unparkSuccessor 方法喚醒隊(duì)列中的下一個(gè)節(jié)點(diǎn)

ReentrantLock.Sync#tryRelease

該方法也體現(xiàn)了鎖重入次數(shù)的操作,源代碼如下:

protected final boolean tryRelease(int releases) {
	// 當(dāng)前鎖線程重入次數(shù)減去要釋放的次數(shù)
    int c = getState() - releases;
    // 當(dāng)前線程不等于鎖持有線程,則判斷中斷監(jiān)聽鎖異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 若減去后的鎖次數(shù)為 0
    if (c == 0) {
    	// 返回 true、設(shè)置鎖持有線程為 null,其他線程就可以競(jìng)爭(zhēng)鎖了
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 遞減鎖重入次數(shù),返回 false,鎖仍然被當(dāng)前線程所持有
    setState(c);
    return free;
}

ReentrantLock.Sync#tryRelease 執(zhí)行流程主要分析如下:

  1. 通過將 AQS#state 屬性值減少傳入的參數(shù)值(參數(shù):1)若減去的結(jié)果狀態(tài)值為 0,就將排它鎖 Owner 持有線程設(shè)置為 null,同時(shí)返回 true,以便于其他的線程有機(jī)會(huì)執(zhí)行競(jìng)爭(zhēng)鎖操作
  2. 若減去的結(jié)果狀態(tài)值不為 0,返回 free 變量默認(rèn)值 false,當(dāng)前線程仍然繼續(xù)持有這把鎖,其他線程暫時(shí)不可以爭(zhēng)搶鎖

在排它鎖中,加鎖時(shí) state 狀態(tài)會(huì)增加 1,在解鎖時(shí)會(huì)減去 1,同一把鎖,在被重入時(shí),可能會(huì)被疊加為 2、3、4 等,只有當(dāng)調(diào)用 unlock 方法次數(shù)與調(diào)用 lock 方法次數(shù)相對(duì)應(yīng),才會(huì)把鎖 Owner 持有線程設(shè)置為空,也只有這種情況下該方法執(zhí)行結(jié)果才有返回 true

AQS#unparkSuccessor

當(dāng) ReentrantLock.Sync#tryRelease 方法執(zhí)行完以后,會(huì)取同步等待隊(duì)列中首節(jié)點(diǎn),喚醒隊(duì)列中下一個(gè)節(jié)點(diǎn)去爭(zhēng)搶這把鎖,該方法源碼如下:

private void unparkSuccessor(Node node) {
    // 獲取傳入節(jié)點(diǎn)的 waitStatus 屬性值  
    int ws = node.waitStatus;
    if (ws < 0)
    	// 小于 0 通過 CAS 將其修改為 0 
        compareAndSetWaitStatus(node, ws, 0);
    // 獲取傳入節(jié)點(diǎn)的后繼節(jié)點(diǎn)
    Node s = node.next;
    // 若后繼節(jié)點(diǎn)為空或者 waitStatus 大于 0 說明它是 CANCELLED-結(jié)束狀態(tài)
    if (s == null || s.waitStatus > 0) {
        s = null;
        // 從尾部節(jié)點(diǎn)開始掃描,找到距離當(dāng)前傳入節(jié)點(diǎn)最近的一個(gè) waitStatus 小于等于 0 的節(jié)點(diǎn)
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // 將同步等待隊(duì)列中 > 最前面的一個(gè)非 CANCELLED 狀態(tài)的 Node 線程進(jìn)行喚醒 
    if (s != null)
        LockSupport.unpark(s.thread);
}

分析一下此方法分別會(huì)作那些事情,如下:

  1. 獲取當(dāng)前傳入節(jié)點(diǎn) Node waitStatus 屬性值,若它小于 0 時(shí),先通過 CAS 操作將其修改為 0

當(dāng)前節(jié)點(diǎn)其實(shí)就是 head 頭節(jié)點(diǎn),喚醒的操作不會(huì)喚醒頭節(jié)點(diǎn),只會(huì)喚醒頭節(jié)點(diǎn)后面不為 CANCELLED 狀態(tài)的首節(jié)點(diǎn) Node 線程

  1. 獲取當(dāng)前節(jié)點(diǎn)的 next 后繼節(jié)點(diǎn),若后繼節(jié)點(diǎn)為空或 waitStatus 大于 0(CANCELLED)那么就會(huì)遍歷該同步等待隊(duì)列,從尾部往前查找的方式,匹配到與當(dāng)前節(jié)點(diǎn)最近的一個(gè)非 CANCELLED 節(jié)點(diǎn),將其設(shè)置為待喚醒的節(jié)點(diǎn)
  2. 若待喚醒的節(jié)點(diǎn)不為空,調(diào)用原生鎖 LockSupport#unpark 方法將其喚醒,以便它可以再次去爭(zhēng)搶鎖

當(dāng)節(jié)點(diǎn)被喚醒后,比如:Thread-A 釋放鎖成功以后,會(huì)調(diào)用 AQS#unparkSuccessor 方法喚醒它的下一個(gè)節(jié)點(diǎn) Thread-B 所持有的(非 CANCELLED)
隨機(jī) Thread-B 被喚醒,它會(huì)繼續(xù)執(zhí)行 AQS#acquireQueued 方法中的循環(huán),執(zhí)行:if (p == head && tryAcquire(arg)) 代碼塊,所以后續(xù)被喚醒的線程都會(huì)是這樣,通過該代碼來確保同步隊(duì)列中的節(jié)點(diǎn)能夠獲取鎖資源

那么為什么在釋放鎖的時(shí)候一定要從尾部開始掃描呢?

回顧一下 AQS#enq 方法執(zhí)行的邏輯,插入新節(jié)點(diǎn)時(shí),它是從隊(duì)列尾部進(jìn)行節(jié)點(diǎn)入隊(duì)的,看下圖紅色所標(biāo)注的,在 CAS 操作成功之后,t.next = node; 操作之前,可能會(huì)存在其他線程調(diào)用 unlock 方法從 head 開始向后遍歷,由于 t.next = node; 還未執(zhí)行也就意味著同步等待隊(duì)列關(guān)系還未建立完整,就會(huì)導(dǎo)致遍歷到原始的尾部節(jié)點(diǎn)時(shí)被中斷 > 隊(duì)列中的鏈表關(guān)系斷鏈了;所以說,從后往前遍歷就不會(huì)出現(xiàn)這個(gè)問題

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

掛起線程被喚醒后執(zhí)行過程

當(dāng)持有鎖的線程調(diào)用 ReentrantLock#unlock 方法,原本被掛起的 Thread-B、Thread-C 線程就有機(jī)會(huì)被喚醒再繼續(xù)執(zhí)行,被喚醒之后的線程會(huì)繼續(xù)執(zhí)行 AQS#acquireQueued 方法內(nèi)的循環(huán),該方法在上面已經(jīng)分析過了,接下來以 Thread-B 被喚醒后為例,看它整個(gè)的執(zhí)行過程以及變化,以流程圖的方式呈現(xiàn)

同步等待隊(duì)列變更結(jié)構(gòu)圖:

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

同步等待隊(duì)列執(zhí)行過程流程圖:

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

博主是以如下源碼,對(duì) ReentrantLock、AQS 核心方法源碼進(jìn)行查看的,分享如下:

public class MultiThreadReentrantLockDemo {
    private static final ReentrantLock LOCK = new ReentrantLock();

    public void threadAProcess() {
        LOCK.lock();
        try {
            System.out.println("執(zhí)行:threadAProcess 方法");
            // 處理業(yè)務(wù)邏輯中....
            // 斷點(diǎn)過程中該時(shí)間可以延長(zhǎng)
            TimeUnit.SECONDS.sleep(6);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            LOCK.unlock();
        }
    }

    public void threadBProcess() {
        LOCK.lock();
        try {
            System.out.println("執(zhí)行:threadBProcess 方法");
            // 處理業(yè)務(wù)邏輯中....
            TimeUnit.SECONDS.sleep(60 * 5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            LOCK.unlock();
        }
    }

    public void threadCProcess() {
        LOCK.lock();
        try {
            System.out.println("執(zhí)行:threadCProcess 方法");
            // 處理業(yè)務(wù)邏輯中....
            TimeUnit.SECONDS.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            LOCK.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MultiThreadReentrantLockDemo multiThreadLock = new MultiThreadReentrantLockDemo();

        new Thread(()-> multiThreadLock.threadAProcess(), "Thread-A").start();

        Thread threadB = new Thread(() -> multiThreadLock.threadBProcess(), "Thread-B");
        threadB.start();
        // 可能會(huì)出現(xiàn) Thread-C 先執(zhí)行的情況,所以先通過 join 方法讓線程 B 先跑完
        threadB.join();

        new Thread(()-> multiThreadLock.threadCProcess(), "Thread-C").start();
    }
}

注意:斷點(diǎn)模式下,要以 Thread 模式執(zhí)行;如下圖:

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

公平鎖、非公平鎖區(qū)別

鎖的公平與否其實(shí)取決于獲取鎖的順序性,若為公平鎖,那么獲取鎖的順序應(yīng)該絕對(duì)符合 FIFO 隊(duì)列 > 先進(jìn)先出的特性,上面所分析的例子都是以非公平鎖(默認(rèn)是非公平鎖)只要 CAS 設(shè)置 AQS#state 屬性值成功,就代表當(dāng)前線程獲取到了鎖,而公平鎖不一樣,差異的地方有如下兩點(diǎn):

1、FairSync#lock、NonfairSync#lock

非公平鎖在獲取鎖時(shí),先通過 CAS 操作進(jìn)行鎖搶占,而公平鎖不會(huì)

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)
2、FairSync#tryAcquire、NonfairSync#tryAcquire

兩者方法之間不同之處在于判斷多了一個(gè)條件:hasQueuedPredecessors,也就是說加入了同步隊(duì)列中當(dāng)前節(jié)點(diǎn)是否有前驅(qū)節(jié)點(diǎn)的判斷,若該方法返回 true,則表示有線程比當(dāng)前線程更早入隊(duì)、更早地請(qǐng)求獲取鎖,因此,需要等待前驅(qū)節(jié)點(diǎn)的線程獲取完再釋放鎖以后才能繼續(xù)獲取鎖!

深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)

public final boolean hasQueuedPredecessors() {
    Node t = tail; // Read fields in reverse initialization order
    Node h = head;
    Node s;
    return h != t &&
        ((s = h.next) == null || s.thread != Thread.currentThread());
}

1、h != t:頭尾節(jié)點(diǎn)是否相同,若相同則表示隊(duì)列中只有一個(gè)節(jié)點(diǎn),即當(dāng)前未發(fā)生鎖競(jìng)爭(zhēng)
假設(shè):當(dāng)前只有線程 Thread-A 一人爭(zhēng)搶鎖,那么 head == null && tail ==null,那么返回 false 會(huì)去爭(zhēng)搶鎖;反之,會(huì)繼續(xù)走第二步的判斷
2、(s = h.next) == null:檢查頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)是否為空,即判斷是否存在后繼節(jié)點(diǎn)
假設(shè):線程 Thread-A、Thread-B 同時(shí)爭(zhēng)搶鎖,Thread-A 搶到了,那么同步等待隊(duì)列中會(huì)有頭節(jié)點(diǎn)、Thread-B 所在節(jié)點(diǎn),條件不滿足返回 false,會(huì)繼續(xù)走第三步的判斷;反之,當(dāng)前只有一個(gè)節(jié)點(diǎn) > 返回 true 不會(huì)去爭(zhēng)搶鎖,不走第三步的判斷
3、s.thread != Thread.currentThread():檢查后繼節(jié)點(diǎn)的線程是否與當(dāng)前線程不同,即判斷后繼節(jié)點(diǎn)持有線程是否為當(dāng)前線程
假設(shè):頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)持有線程就是當(dāng)前的線程,會(huì)返回 false 會(huì)去爭(zhēng)搶鎖;反之,頭節(jié)點(diǎn)的后繼節(jié)點(diǎn)持有線程不是當(dāng)前的線程,會(huì)返回 true 不會(huì)去爭(zhēng)搶鎖,它會(huì)進(jìn)入到排隊(duì)模式?。?!

總結(jié)

ReentrantLock 基于悲觀鎖實(shí)現(xiàn)(LockSupport),但是在處理 AQS#state 鎖狀態(tài)時(shí)是基于 CAS 樂觀鎖實(shí)現(xiàn)的,兩者在不同場(chǎng)景下都會(huì)各自的好處,因?yàn)榍罢咭呀?jīng)悲觀鎖,后者再用 CAS 操作并沒有任何問題

>在這里其實(shí)就是偷換概念了,不一定用了悲觀鎖就不能用樂觀鎖

該篇博文介紹了 JUC 組件下 ReentrantLock 核心概念、使用、源碼以及 AQS 基礎(chǔ)組件的核心方法,闡述了 AQS 內(nèi)部實(shí)現(xiàn)、數(shù)據(jù)結(jié)構(gòu)以及節(jié)點(diǎn)變更過程,在后面,看 ReentrantLock 是如何基于 AQS 核心方法去完成其內(nèi)部鎖競(jìng)爭(zhēng)工作的、鎖釋放后如何喚醒其他節(jié)點(diǎn)線程,全文上下以畫圖+文字加以說明,不限于時(shí)序圖、結(jié)構(gòu)圖、流程圖,最后說明了在 ReentrantLock 公平鎖、非公平鎖之間的區(qū)別,希望能夠幫助你快速理解 AQS 內(nèi)部如何巧妙處理高并發(fā)場(chǎng)景問題的

如果覺得博文不錯(cuò),關(guān)注我 vnjohn,后續(xù)會(huì)有更多實(shí)戰(zhàn)、源碼、架構(gòu)干貨分享!

推薦專欄:Spring、MySQL,訂閱一波不再迷路

大家的「關(guān)注?? + 點(diǎn)贊?? + 收藏?」就是我創(chuàng)作的最大動(dòng)力!謝謝大家的支持,我們下文見!文章來源地址http://www.zghlxwxcb.cn/news/detail-501727.html

到了這里,關(guān)于深入源碼解析 ReentrantLock、AQS:掌握 Java 并發(fā)編程關(guān)鍵技術(shù)的文章就介紹完了。如果您還想了解更多內(nèi)容,請(qǐng)?jiān)谟疑辖撬阉鱐OY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!

本文來自互聯(lián)網(wǎng)用戶投稿,該文觀點(diǎn)僅代表作者本人,不代表本站立場(chǎng)。本站僅提供信息存儲(chǔ)空間服務(wù),不擁有所有權(quán),不承擔(dān)相關(guān)法律責(zé)任。如若轉(zhuǎn)載,請(qǐng)注明出處: 如若內(nèi)容造成侵權(quán)/違法違規(guī)/事實(shí)不符,請(qǐng)點(diǎn)擊違法舉報(bào)進(jìn)行投訴反饋,一經(jīng)查實(shí),立即刪除!

領(lǐng)支付寶紅包贊助服務(wù)器費(fèi)用

相關(guān)文章

  • 從ReentrantLock角度解析AQS

    從ReentrantLock角度解析AQS

    是它,是它,就是它,并發(fā)包的基石; 閑來不卷,隨便聊一點(diǎn)。 一般情況下,大家系統(tǒng)中至少也是JDK8了,那想必對(duì)于JDK5加入的一系列功能并不陌生吧。那時(shí)候重點(diǎn)加入了 java.util.concurrent 并發(fā)包,我們簡(jiǎn)稱為JUC。JUC下提供了很多并發(fā)編程實(shí)用的工具類,比如并發(fā)鎖lock、原子

    2023年04月14日
    瀏覽(19)
  • 深入理解高并發(fā)編程 - 深度解析Thread 類的源碼

    先看源碼:Thread 類實(shí)現(xiàn)了 Runnable 接口 而 Runnable 被@FunctionalInterface 注解標(biāo)記為函數(shù)式接口,Runnable 接口源代碼 再來看看@FunctionalInterface 注解的源碼 FunctionalInterface 用于標(biāo)記類或接口是否是一個(gè)函數(shù)式接口,并在運(yùn)行時(shí)可用于反射獲取信息。 這段代碼是 Thread 類的一個(gè)靜態(tài)初

    2024年02月09日
    瀏覽(30)
  • AQS源碼分析——以ReentrantLock為例

    AQS源碼分析——以ReentrantLock為例

    AQS自身屬性: Node屬性: 1. 以ReentrantLock為例,其他類舉一反三,方法lock() 2. Lock接口實(shí)現(xiàn)類,基本都是通過【聚合】了一個(gè) 【隊(duì)列同步器】的子類完成線程訪問控制的Sync實(shí)現(xiàn)lock;Sync繼承AQS;Sync又有兩個(gè)實(shí)現(xiàn)類。 ?公平鎖與非公平鎖的實(shí)現(xiàn): 接下來以非公平鎖為突破口:

    2024年02月10日
    瀏覽(22)
  • 萬字長(zhǎng)文解析AQS抽象同步器核心原理(深入閱讀AQS源碼)

    萬字長(zhǎng)文解析AQS抽象同步器核心原理(深入閱讀AQS源碼)

    在爭(zhēng)用激烈的場(chǎng)景下使用基于CAS自旋實(shí)現(xiàn)的輕量級(jí)鎖有兩個(gè)大的問題: CAS惡性空自旋會(huì)浪費(fèi)大量的CPU資源。 在SMP架構(gòu)的CPU上會(huì)導(dǎo)致“總線風(fēng)暴”。 解決CAS惡性空自旋的有效方式之一是以空間換時(shí)間,較為常見的方案有兩種:分散操作熱點(diǎn)、使用隊(duì)列削峰。 JUC并發(fā)包使用的是

    2024年02月11日
    瀏覽(20)
  • java并發(fā)編程 AbstractQueuedSynchronizer(AQS)詳解一

    AQS在類的注釋上說的已經(jīng)很明白,提供一個(gè)框架,用于實(shí)現(xiàn)依賴先進(jìn)先出(FIFO)等待隊(duì)列的阻塞鎖和相關(guān)同步器(信號(hào)量、事件等)。此類被設(shè)計(jì)做為大多數(shù)類型的同步器的一個(gè)有用的基礎(chǔ)類,這些同步器依賴于單個(gè)原子int值(state字段)來表示狀態(tài)。 java 并發(fā)編程系列文章

    2024年02月10日
    瀏覽(21)
  • Java——并發(fā)編程(CAS、Lock和AQS)

    Java——并發(fā)編程(CAS、Lock和AQS)

    答: Lock 接口比同步方法和同步塊提供了 更具擴(kuò)展性的鎖操作 。他們?cè)试S更靈活的結(jié)構(gòu),可以具有完全不同的性質(zhì),并且可以支持多個(gè)相關(guān)類的條件對(duì)象。 可以使鎖更公平; 可以使線程在等待鎖的時(shí)候響應(yīng)中斷; 可以讓線程嘗試獲取鎖,并在無法獲取鎖的時(shí)候立即返回或

    2024年02月06日
    瀏覽(37)
  • 掌握Go并發(fā):Go語(yǔ)言并發(fā)編程深度解析

    掌握Go并發(fā):Go語(yǔ)言并發(fā)編程深度解析

    ??? 個(gè)人主頁(yè) :鼠鼠我捏,要死了捏的主頁(yè)? ??? 系列專欄 :Golang全棧-專欄 ??? 個(gè)人學(xué)習(xí)筆記,若有缺誤,歡迎評(píng)論區(qū)指正 ? 前些天發(fā)現(xiàn)了一個(gè)巨牛的人工智能學(xué)習(xí)網(wǎng)站,通俗易懂,風(fēng)趣幽默,忍不住分享一下給大家。點(diǎn)擊跳轉(zhuǎn)到網(wǎng)站AI學(xué)習(xí)網(wǎng)站。 當(dāng)我們開發(fā)一個(gè)W

    2024年02月20日
    瀏覽(23)
  • 深入理解高并發(fā)編程 - 深度解析ScheduledThreadPoolExecutor

    ScheduledThreadPoolExecutor 繼承自 ThreadPoolExecutor 并實(shí)現(xiàn)了 ScheduledExecutorService 接口,這使得它可以同時(shí)充當(dāng)線程池和定時(shí)任務(wù)調(diào)度器。 構(gòu)造方法接收一個(gè) corePoolSize 參數(shù),它表示線程池中的核心線程數(shù)。核心線程是一直保持存活的線程,即使沒有任務(wù)執(zhí)行,以便支持定時(shí)任務(wù)的調(diào)

    2024年02月12日
    瀏覽(32)
  • 并發(fā)編程之可重入鎖ReentrantLock

    并發(fā)編程之可重入鎖ReentrantLock

    大家都知道在并發(fā)編程中一般會(huì)用到多線程技術(shù),多線程技術(shù)可以大大增加系統(tǒng)QPS/TPS。但是在一些特殊的業(yè)務(wù)場(chǎng)景下我們需要限制線程的并發(fā)數(shù)目,比如秒殺系統(tǒng)、多種商品金額疊加運(yùn)算等等都是需要限制線程數(shù)量。特別是在分布式微服務(wù)架構(gòu),多線程同步問題尤為明顯。一

    2023年04月25日
    瀏覽(43)
  • JUC并發(fā)編程之AQS原理

    JUC并發(fā)編程之AQS原理

    全稱是 AbstractQueuedSynchronizer,是阻塞式鎖和相關(guān)的同步器工具的框架 特點(diǎn): 用 state 屬性來表示資源的狀態(tài)(分獨(dú)占模式和共享模式),子類需要定義如何維護(hù)這個(gè)生態(tài),控制如何獲取鎖和釋放鎖 getState - 獲取 state 狀態(tài) setState - 設(shè)置 state 狀態(tài) compareAndSetState - cas 機(jī)制設(shè)置 s

    2023年04月18日
    瀏覽(30)

覺得文章有用就打賞一下文章作者

支付寶掃一掃打賞

博客贊助

微信掃一掃打賞

請(qǐng)作者喝杯咖啡吧~博客贊助

支付寶掃一掃領(lǐng)取紅包,優(yōu)惠每天領(lǐng)

二維碼1

領(lǐng)取紅包

二維碼2

領(lǐng)紅包