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

ReentrantLock 非公平鎖實現(xiàn)原理詳解

 更新時間:2022年12月14日 10:44:01   作者:小海編碼日記  
這篇文章主要為大家介紹了ReentrantLock 非公平鎖實現(xiàn)原理詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪

正文

在線程間通信方式2一節(jié)中,我們了解了Lock,Condition和ReentrantLock,學(xué)習(xí)了簡單使用Condition和RentrantLock完成線程間通信,從文章中我們了解到ReentrantLock是Lock接口的一個最常用的實現(xiàn)類,ReentrantLock是獨占鎖,獨占鎖的場景下又支持公平鎖和非公平鎖,那么在源碼實現(xiàn)中,ReentrantLock繼承關(guān)系,實現(xiàn)結(jié)構(gòu)又是怎樣的呢?

ReentrantLock繼承關(guān)系及關(guān)聯(lián)類

在多線程與鎖中,我們了解到ReentrantLock是支持公平鎖和非公平鎖的,對應(yīng)的構(gòu)造函數(shù)源碼如下:

 // ReentrantLock.java
 public ReentrantLock(boolean fair) {
     sync = fair ? new FairSync() : new NonfairSync();
 }

可以看到如果是公平鎖則創(chuàng)建FairSync對象,如果是非公平鎖則創(chuàng)建NonfairSync,再結(jié)合Lock接口核心的lock,tryLock,unlock函數(shù)源碼(Lock接口在線程間通信方式2中有介紹)可以看出,在ReentrantLock中,使用FairSync或NonfairSync代理了鎖狀態(tài)管理,lock,tryLock和unlock實現(xiàn)源碼如下:

 // ReentrantLock.java
 public void lock() {
     sync.lock();
 }
 ?
 public boolean tryLock() {
     return sync.nonfairTryAcquire(1);
 }
 ?
 public void unlock() {
     sync.release(1);
 }

由此,進一步梳理ReentrantLock實現(xiàn),可以得到下圖:

上圖中一些生僻類及其作用見下表:

類名說明備注
FairSyncReentrantLock中公平鎖的實現(xiàn)類/
NonfairSyncReentrantLock中非公平鎖的實現(xiàn)類/
Sync公平鎖和非公平鎖實現(xiàn)類的共同父類,使用AbstractQueuedSynchronizer狀態(tài)記錄鎖的持有數(shù)據(jù)/
AbstractQueuedSynchronizerAbstractQueuedSynchronizer簡稱AQS,一個用于實現(xiàn)阻塞鎖和同步器的工具類,其內(nèi)部維護一個先進先出的等待隊列(真實數(shù)據(jù)結(jié)構(gòu)是雙向鏈表),依賴一個int型的數(shù)據(jù)管理同步狀態(tài)/
AbstractOwnableSynchronizerAQS的父類,定義了一個線程獨占的同步器,用于實現(xiàn)創(chuàng)建鎖和鎖占有標(biāo)記的基礎(chǔ)類,其內(nèi)部使用exclusiveOwnerThread記錄當(dāng)前占用鎖的線程/
Serializable序列化接口/

ReentrantLock.lock流程分析

跟蹤ReentrantLock.lock調(diào)用流程,可以得到下面的時序圖(以非公平鎖為例分析):

上圖中描述了ReentrantLock中l(wèi)ock在資源空和資源被占用的情況下的執(zhí)行流程,接下來我們來看下其內(nèi)部的細節(jié)實現(xiàn)。

ReentrantLock.lock獲取鎖成功

NonfairSynck類中的lock代碼如下所示:

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

可以看出,我們在調(diào)用了ReentrantLock.lock后,NonfairSync會首先嘗試通過CAS將資源占用狀態(tài)置為1(compareAndSetState,默認值0,期望值1),如果執(zhí)行成功,說明當(dāng)前獲取共享資源成功,將當(dāng)前線程設(shè)置為獨占鎖持有者,當(dāng)前線程繼續(xù)執(zhí)行。

compareAndSetState

compareAndSetState操作的是AQS中聲明的一個int型的值,如果其值為0,表示當(dāng)前鎖空閑,如果有線程到來可以占用鎖,如果值大于1,表示當(dāng)前鎖被占用,為保證多線程對該值的操作實時可見,使用volatile修飾該變量,相關(guān)代碼如下:

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

setExclusiveOwnerThread

setExclusiveOwnerThread最終是將當(dāng)前線程設(shè)置到AbstractOwnableSynchronizer中定義的exclusiveOwnerThread中,代碼如下:

 // AbstractOwnableSynchronizer.java
 private transient Thread exclusiveOwnerThread;
 ?
 protected final void setExclusiveOwnerThread(Thread thread) {
     exclusiveOwnerThread = thread;
 }

transient關(guān)鍵字:

transient關(guān)鍵字的主要作用是讓某些被transient關(guān)鍵字修飾的變量不被序列化,如果對transient修飾的變量執(zhí)行了序列化,則該變量會重新執(zhí)行默認初始化,反序列化得到的對象是null

ReentrantLock.lock獲取鎖失敗

前文中可以看到如果compareAndSetState執(zhí)行返回false的話,就說明當(dāng)前共享資源被占用,隨后走else邏輯,執(zhí)行acquire(1),其內(nèi)容如下:

 // NonfairSync.java
 final void lock() {
     if (compareAndSetState(0, 1))
         setExclusiveOwnerThread(Thread.currentThread());
     else
         acquire(1);
 }
 ?
 // AbstractQueuedSynchronizer.java
 public final void acquire(int arg) {
     if (!tryAcquire(arg) &&
         acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
         selfInterrupt();
 }

其內(nèi)部主要由兩塊組成,tryAcquire和acquireQueued,其中tryAcquire再次嘗試獲取鎖,如果獲取成功,則流程結(jié)束,當(dāng)前線程正常繼續(xù)執(zhí)行,如果獲取失敗,則執(zhí)行acquireQueued方法(由于使用且操作符連接tryAcquire和acquireQueued,所以只有tryAcquire返回false的時候,才會執(zhí)行acquireQueued方法),該方法接受addWaiter返回的Node參數(shù),下面來詳細看下兩個函數(shù)的具體實現(xiàn)。

tryAcquire

tryAcquire在NonfairSync中的實現(xiàn)如下,其最終調(diào)用到的是Sync類的nonfairTryAcquire方法:

 // NonfairSync.java
 protected final boolean tryAcquire(int acquires) {
     return nonfairTryAcquire(acquires);
 }
 // Sync.java
 final boolean nonfairTryAcquire(int acquires) {
     final Thread current = Thread.currentThread();
     // 獲取當(dāng)前同步資源狀態(tài)
     int c = getState();
     // 同步資源空閑
     if (c == 0) {
         // 占用同步資源更新資源狀態(tài)
         if (compareAndSetState(0, acquires)) {
             // 設(shè)置當(dāng)前線程為資源對應(yīng)的獨占鎖持有者
             setExclusiveOwnerThread(current);
             return true;
         }
     }
     // 如果當(dāng)前線程就是持有鎖線程,更新鎖狀態(tài),直接持有鎖
     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;
 }

可以看到在tryAcquire中實際上也是嘗試獲取鎖的過程,首先檢查當(dāng)前當(dāng)前同步資源狀態(tài),如果不可獲取,則檢查當(dāng)前線程時否是持鎖線程,是的話則直接獲取鎖(可重入鎖的實現(xiàn)),更新同步資源狀態(tài),如果均失敗,則返回false。

acquireQueued

acquireQueued在AQS中關(guān)聯(lián)的核心代碼如下所示,可以看出主要包含addWaiter和acquireQueued兩部分:

addWaiter

// AbstractQueuedSynchronizer.java
private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        if (t == null) { // Must initialize
            if (compareAndSetHead(new Node()))
                tail = head;
        } else {
            node.prev = t;
            if (compareAndSetTail(t, node)) {
                t.next = node;
                return t;
            }
        }
    }
}
// AbstractQueuedSynchronizer.java
private Node addWaiter(Node mode) {
    // 創(chuàng)建新的Node對象
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}

可以看到不管是addWaiter還是enq,其最終目標(biāo)都是基于當(dāng)前線程創(chuàng)建新的Node對象,將新的Node對象添加在隊尾,前文提到AQS中維護了一個先進先出的隊列,其數(shù)據(jù)結(jié)構(gòu)本質(zhì)是雙向鏈表,這里的head,tail就是鏈表的具體實現(xiàn)。Node類中包含了指向前一個元素和后一個元素的引用,Node的聲明如下:

// Node實體類
static final class Node {
    ...
    // 前一個元素
    volatile Node prev;
    // 下一個元素
    volatile Node next;
    // 線程對象
    volatile Thread thread;
    Node() {    // Used to establish initial head or SHARED marker
    }
    Node(Thread thread, Node mode) {     // Used by addWaiter
        this.nextWaiter = mode;
        this.thread = thread;
    }
    Node(Thread thread, int waitStatus) { // Used by Condition
        this.waitStatus = waitStatus;
        this.thread = thread;
    }
    ...
}
// AbstractQueuedSynchronizer.java中聲明Node對象
// 鏈表頭
private transient volatile Node head;
// 鏈表尾部
private transient volatile Node tail;

acquireQueued

acquireQueued源碼如下:

// AbstractQueuedSynchronizer.java
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

可以看到在addWaiter中成功將新的Node添加到隊尾后,acquireQueued中當(dāng)前線程會自旋,嘗試獲取鎖,如果獲取失敗,則執(zhí)行shouldParkAfterFailedAcquire和parkAndCheckInterrupt,這兩個函數(shù)都返回true則執(zhí)行selfInterrupt方法(代碼見acquire函數(shù)部分)。

shouldParkAfterFailedAcquire用于判斷是否應(yīng)該對當(dāng)前線程阻塞,如果是的話則返回true,parkAndCheckInterrupt用于執(zhí)行線程阻塞并且判斷當(dāng)前線程是否處于interrupt狀態(tài),如果是則會返回true。通過源碼可以看到其是通過LockSupport.park進行線程狀態(tài)切換的,代碼如下:

// AbstractQueuedSynchronizer.java
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

LockSupport.park作用

LockSupport.park用于在許可證不可用時阻塞禁用當(dāng)前線程,如果許可證可用,則該許可證被消耗,并且當(dāng)前調(diào)用立即返回,否則,出于線程調(diào)度的目的,當(dāng)前線程將被阻塞禁用,并進入休眠狀態(tài),直到發(fā)生以下三種情況之一:

  • 其他線程以當(dāng)前線程為參數(shù)調(diào)用unpark
  • 其他線程中斷當(dāng)前線程
  • 調(diào)用發(fā)生異常

結(jié)合上文,我們可以得出ReentrantLock.lock執(zhí)行的一般流程如下所示:

ReentrantLock.unlock流程分析

跟蹤ReentrantLock.unlock調(diào)用流程,可以得到下面的時序圖(以非公平鎖為例分析):

可以看到對于ReentrantLock.unlock流程而言,其核心實現(xiàn)函數(shù)是tryRelease和unparkSuccessor,release函數(shù)如下所示:

// ReentrantLock.java
public void unlock() {
    sync.release(1);
}
// AQS.java
public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

從代碼中可以看到當(dāng)tryRelease執(zhí)行完成返回true后,我們會獲取鏈表首位元素,當(dāng)首位元素不為空且等待狀態(tài)(waitStatus)不等于0時,執(zhí)行unparkSuccessor,在這里我們又遇到了Node類的另一個核心元素waitStatus,其聲明如下:

static final class Node {
    ...
    // waitStatus的四種可能取值,表示當(dāng)前節(jié)點及其后續(xù)節(jié)點對應(yīng)線程的運行狀態(tài)
    static final int CANCELLED =  1;
    static final int SIGNAL    = -1;
    static final int CONDITION = -2;
    static final int PROPAGATE = -3;
    // 聲明waitStatus
    volatile int waitStatus;
    ...
}

四種取值含義如下所示:

  • SIGNAL:取值為-1,該節(jié)點的后續(xù)節(jié)點處于被阻塞或即將阻塞的狀態(tài),因此當(dāng)前節(jié)點的線程在釋放鎖或取消時必須解除后繼節(jié)點的阻塞狀態(tài),使后續(xù)節(jié)點的線程得以正常運行
  • CANCELLED:取值為1,當(dāng)前節(jié)點對應(yīng)的線程由于超時或中斷而被取消,Node的waitStatus取該值,進入取消狀態(tài)后節(jié)點狀態(tài)不再變化
  • CONDITION:取值為-2,該節(jié)點當(dāng)前在等待隊列中,節(jié)點對應(yīng)的線程等待Condition,當(dāng)其他線程對Condition調(diào)用了signal方法后,該節(jié)點從等待隊列中轉(zhuǎn)入鏈表中,進行同步狀態(tài)的獲取
  • PROPAGATE:取值為-3,在共享鎖實現(xiàn)中使用,當(dāng)前節(jié)點線程處于可運行狀態(tài)

為了簡化使用,對于waitStatus取值并沒有按照數(shù)字遞增或遞減排列進行取值,如果該節(jié)點取值為非負值,則代表不需要將操作同步到其他節(jié)點,對于普通Node節(jié)點而言,waitStatus字段初始化為0,對于條件節(jié)點,該字段初始化為1,在代碼中使用CAS對該字段進行修改。

下面我們來分別看下這兩個核心函數(shù)的實現(xiàn)

tryRelease

tryRelease主要用于對鎖狀態(tài)標(biāo)記進行清理,函數(shù)實現(xiàn)如下所示:

protected final boolean tryRelease(int releases) {
    // 獲取AQS的鎖狀態(tài)標(biāo)記,計算剩余鎖狀態(tài)標(biāo)記
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    // 鎖狀態(tài)標(biāo)記為0,當(dāng)前沒有其他線程持有鎖,鎖處于空閑狀態(tài),設(shè)置鎖持有者為null
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 更新鎖狀態(tài)
    setState(c);
    return free;
}

在ReentrantLock獨占鎖實現(xiàn)的場景下,state鎖狀態(tài)標(biāo)記取值只有兩個,0和1(前文lock過程中也有分析通過CAS改變state狀態(tài)時,一直是從0到1的修改),進而當(dāng)當(dāng)前線程執(zhí)行tryRelease后,state鎖狀態(tài)標(biāo)記取值更新為0,表示當(dāng)前鎖處于空閑狀態(tài),隨后自然要喚醒Node鏈表中的其他節(jié)點去獲取鎖啦。

unparkSuccessor

unparkSuccessor函數(shù)主要用于更新Node節(jié)點的waitStatus狀態(tài)并按需將Node鏈表中阻塞的線程喚醒執(zhí)行,實現(xiàn)如下所示:

private void unparkSuccessor(Node node) {
    // 獲取頭節(jié)點的waitStatus狀態(tài),將頭節(jié)點的waitStatus狀態(tài)設(shè)置為0,head waitStatus恢復(fù)初始狀態(tài)
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);
    // 獲取頭節(jié)點的下一個節(jié)點,如果下一個節(jié)點為null或者狀態(tài)為取消狀態(tài)(waitStatus大于0只有取消狀態(tài)),則從尾節(jié)點開始遍歷,查找未被取消的后續(xù)節(jié)點對象
    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    // 對上一步獲取到的節(jié)點對應(yīng)的線程執(zhí)行unpark喚醒,去搶占鎖
    if (s != null)
        LockSupport.unpark(s.thread);
}

從前面可以看出這里傳入的node是當(dāng)前的head(鏈表頭),判斷當(dāng)前頭節(jié)點的waitStatus,如果不是初始值則重置為初始值,隨著查找下一個要被喚醒的節(jié)點,查找到后,喚醒節(jié)點對應(yīng)的線程,讓線程去嘗試搶占鎖。

結(jié)合上文,我們可以得出ReentrantLock.unlock執(zhí)行的一般流程如下所示:

以上就是ReentrantLock 非公平鎖實現(xiàn)原理詳解的詳細內(nèi)容,更多關(guān)于ReentrantLock 非公平鎖的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • 利用Mybatis自定義排序規(guī)則實現(xiàn)復(fù)雜排序

    利用Mybatis自定義排序規(guī)則實現(xiàn)復(fù)雜排序

    本文主要介紹了利用Mybatis自定義排序規(guī)則實現(xiàn)復(fù)雜排序,文中通過示例代碼介紹的非常詳細,對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2025-09-09
  • 解析阿里GTS開源版本fescar分布式事務(wù)

    解析阿里GTS開源版本fescar分布式事務(wù)

    這篇文章主要為大家介紹解析阿里GTS開源版本fescar分布式事務(wù)的原理及使用說明,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多進步
    2022-02-02
  • JAVA中的Token 基于Token的身份驗證實例

    JAVA中的Token 基于Token的身份驗證實例

    這篇文章主要介紹了JAVA中的Token 基于Token的身份驗證實例,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-08-08
  • Java如何通過反射獲取Constructor、Field、Method對象

    Java如何通過反射獲取Constructor、Field、Method對象

    反射指的是對象的反向處理操作,根據(jù)對象取得對象的來源信息,在反射的世界里面,看重的不再是一個對象,而是對象身后的組成,下面這篇文章主要給大家介紹了關(guān)于Java如何通過反射獲取Constructor、Field、Method對象的相關(guān)資料,需要的朋友可以參考下
    2022-06-06
  • Springboot集成magic-api的詳細過程

    Springboot集成magic-api的詳細過程

    這篇文章主要介紹了Springboot集成magic-api的相關(guān)知識,本文結(jié)合實例代碼給大家介紹的非常詳細,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價值,需要的朋友可以參考下
    2023-06-06
  • 使用JSON.toJSONString()返回{}的原因

    使用JSON.toJSONString()返回{}的原因

    這篇文章主要介紹了使用JSON.toJSONString()返回{}的原因,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-02-02
  • java面向?qū)ο笤O(shè)計原則之開閉原則示例解析

    java面向?qū)ο笤O(shè)計原則之開閉原則示例解析

    這篇文章主要介紹了java面向?qū)ο笤O(shè)計原則之開閉原則的示例解析,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進步,早日升職加薪
    2021-10-10
  • Java中的DecimalFormat用法解析

    Java中的DecimalFormat用法解析

    這篇文章主要介紹了Java中的DecimalFormat用法解析,DecimalFormat是Java中用于格式化數(shù)字的類,它提供了一種簡單而靈活的方式來格式化數(shù)字,包括指定小數(shù)位數(shù)、千位分隔符、貨幣符號等,需要的朋友可以參考下
    2023-10-10
  • SpringBoot如何優(yōu)雅的輸出異常信息

    SpringBoot如何優(yōu)雅的輸出異常信息

    在Java中,異常(Exception)是Java程序在運行過程中出現(xiàn)的一種特殊情況,會中斷正常的程序流程,異??梢允沁\行時錯誤,也可以是編程錯誤,本文將給大家詳細的介紹一下SpringBoot如何優(yōu)雅的輸出異常信息,需要的朋友可以參考下
    2023-09-09
  • SpringBoot原生組件注入實現(xiàn)兩種方式介紹

    SpringBoot原生組件注入實現(xiàn)兩種方式介紹

    SpringBoot是Spring全家桶的成員之一,基于約定優(yōu)于配置的思想(即有約定默認值,在不配置的情況下會使用默認值,在配置文件下配置的話會使用配置的值)。SpringBoot是一種整合Spring技術(shù)棧的方式(或者說是框架),同時也是簡化Spring的一種快速開發(fā)的腳手架
    2022-10-10

最新評論

鸡泽县| 东平县| 黎平县| 视频| 中宁县| 开鲁县| 临高县| 常州市| 丽水市| 商都县| 锡林郭勒盟| 阳西县| 昔阳县| 改则县| 肃南| 清水河县| 邢台市| 靖安县| 奈曼旗| 舞阳县| 上蔡县| 将乐县| 临武县| 桃园市| 来安县| 肃北| 蒙山县| 浦城县| 嘉荫县| 馆陶县| 古田县| 黎川县| 二连浩特市| 峨山| 山阴县| 阿坝| 喀喇| 临西县| 洪洞县| 乐安县| 苍山县|