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

關于ReentrantLock的實現(xiàn)原理解讀

 更新時間:2023年06月19日 10:08:30   作者:盛夏溫暖流年  
這篇文章主要介紹了關于ReentrantLock的實現(xiàn)原理,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

ReentrantLock 簡介

ReentrantLock 實現(xiàn)了 Lock 接口,是一種可重入的獨占鎖。

相比于 synchronized 同步鎖,ReentrantLock 更加靈活,擁有更加強大的功能,比如可以實現(xiàn)公平鎖機制。

首先,先來了解一下什么是公平鎖機制。

ReentrantLock 的公平鎖機制

我們知道,ReentrantLock 分為公平鎖非公平鎖,可以通過構造方法來指定具體類型:

//默認非公平鎖
public ReentrantLock() {
    sync = new NonfairSync();
}
//公平鎖
public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

公平鎖

在多個線程競爭獲取鎖時,公平鎖傾向于將訪問權授予等待時間最長的線程。

也就是說,公平鎖相當于有一個線程等待隊列,先進入隊列的線程會先獲得鎖,按照 "FIFO(先進先出)" 的原則,對于每一個等待線程都是公平的。

非公平鎖

非公平鎖是搶占模式,線程不會關注隊列中是否存在其他線程,也不會遵守先來后到的原則,直接嘗試獲取鎖。

接下來進入正題,一起分析下 ReentrantLock 的底層是如何實現(xiàn)的。

ReentrantLock 的底層實現(xiàn)

ReentrantLock 實現(xiàn)的前提是 AbstractQueuedSynchronizer(抽象隊列同步器),簡稱 AQS,是 java.util.concurrent 的核心,

常用的線程并發(fā)類 CountDownLatch、CyclicBarrier、Semaphore、ReentrantLock 等都包括了一個繼承自 AQS 抽象類的內(nèi)部類。

同步標志位 state

AQS 內(nèi)部維護了一個同步標志位 state,用來實現(xiàn)同步加鎖控制:

private volatile int state;

同步標志位 state 的初始值為 0,線程每加一次鎖,state 就會加 1,也就是說,已經(jīng)獲得鎖的線程再次加鎖,state 值會再次加 1。

可以看出,state 實際上表示的是已獲得鎖的線程進行加鎖操作的次數(shù)。

CLH 隊列

除了 state 同步標志位外,AQS 內(nèi)部還使用一個 FIFO 的隊列(也叫 CLH 隊列)來表示排隊等待鎖的線程,當線程爭搶鎖失敗后會封裝成 Node 節(jié)點加入 CLH 隊列中去。

Node 的代碼實現(xiàn):

static final class Node {
      // 標識當前節(jié)點在共享模式
      static final Node SHARED = new Node();
      // 標識當前節(jié)點在獨占模式
      static final Node EXCLUSIVE = null;
      static final int CANCELLED =  1;
      static final int SIGNAL    = -1;
      static final int CONDITION = -2;
      static final int PROPAGATE = -3;
    volatile int waitStatus;
      //前驅(qū)節(jié)點
      volatile Node prev;
      //后驅(qū)節(jié)點
      volatile Node next;
      //當前線程
      volatile Thread thread;
      //存儲在condition隊列中的后繼節(jié)點
      Node nextWaiter;
      //是否為共享鎖
      final boolean isShared() {
            return nextWaiter == SHARED;
      }
      final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
      }
      Node() {}
      Node(Thread thread, Node mode) {
            this.nextWaiter = mode;
            this.thread = thread;
      }
      Node(Thread thread, int waitStatus) {
            this.waitStatus = waitStatus;
            this.thread = thread;
      }
}

分析代碼可知, 每個 Node 節(jié)點都有兩個指針,分別指向直接后繼節(jié)點和直接前驅(qū)節(jié)點。

Node 節(jié)點的變化過程

當出現(xiàn)鎖競爭以及釋放鎖的時候,AQS 同步隊列中的 Node 節(jié)點會發(fā)生變化,如下圖所示:

  

線程封裝成 Node 節(jié)點追加到隊列末尾,設置當前節(jié)點的 prev 節(jié)點和 next 節(jié)點的指向;通過 CAS 將 tail 重新指向新的尾部節(jié)點,即當前插入的 Node 節(jié)點;

head 節(jié)點表示獲取鎖成功的節(jié)點,當頭結點釋放鎖后,會喚醒后繼節(jié)點,如果后繼節(jié)點獲得鎖成功,就會把自己設置為頭結點,節(jié)點的變化過程如下:

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

和設置 tail 的重新指向不同,設置 head 節(jié)點不需要用 CAS,是因為設置 head 節(jié)點是由獲得鎖的線程來完成的,而同步鎖只能由一個線程獲得,所以不需要 CAS 保證。

只需要把 head 節(jié)點設置為原首節(jié)點的后繼節(jié)點,并且斷開原 head 節(jié)點的 next 引用即可。

除了前驅(qū)和后繼節(jié)點,Node 類中還包括了 SHARED 和 EXCLUSIVE 節(jié)點,它們起到了什么作用呢?這就不得不介紹一下 AQS 的兩種資源共享模式了。

AQS 的資源共享模式

AQS 通過 EXCLUSIVE 和 SHARED 兩個變量來定義獨占模式共享模式。

獨占模式

獨占模式是最常用的模式,使用范圍很廣,比如 ReentrantLock 的加鎖和釋放鎖就是使用獨占模式實現(xiàn)的。

獨占模式中的核心加鎖方法是 acquire()

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

這里首先調(diào)用 tryAcquire() 方法嘗試獲取鎖,也就是嘗試通過 CAS 修改 state 為 1,如果發(fā)現(xiàn)鎖已經(jīng)被當前線程占用,就執(zhí)行重入,也就是給 state+1;

如果鎖被其他線程占有,那么當前線程執(zhí)行 tryAcquire 返回失敗,則會執(zhí)行 addWaiter() 方法在等待隊列中添加一個獨占式節(jié)點,addWaiter() 方法實現(xiàn)如下:

    private Node addWaiter(Node mode) {
        //創(chuàng)建一個節(jié)點,此處mode是獨占式的
        Node node = new Node(mode);
        for (;;) {
            Node oldTail = tail;
            if (oldTail != null) {
                // 如果tail節(jié)點非空,就將新節(jié)點的前節(jié)點設置為tail節(jié)點,并將tail指向新節(jié)點
                node.setPrevRelaxed(oldTail);
                //CAS將tail更新為新節(jié)點
                if (compareAndSetTail(oldTail, node)) {
                    //把原tail的next設為當前節(jié)點
                    oldTail.next = node;
                    return node;
                }
            } else {
                //還沒有初始化,就調(diào)用initializeSyncQueue()方法初始化
                initializeSyncQueue();
            }
        }
    }

寫入隊列后,需要掛起當前線程,代碼如下:

/**
 * 已經(jīng)入隊的線程嘗試獲取鎖
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true; //標記是否成功獲取鎖
    try {
        boolean interrupted = false; //標記線程是否被中斷過
        for (;;) {
            final Node p = node.predecessor(); //獲取前驅(qū)節(jié)點
            //如果前驅(qū)是head,即該結點是第二位,有資格去嘗試獲取鎖
            if (p == head && tryAcquire(arg)) {
                setHead(node); // 獲取成功,將當前節(jié)點設置為head節(jié)點
                p.next = null; // 原h(huán)ead節(jié)點出隊
                failed = false; //獲取成功
                return interrupted; //返回是否被中斷過
            }
            // 判斷獲取失敗后是否可以掛起,若可以則掛起
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                // 線程若被中斷,設置interrupted為true
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

再看下 shouldParkAfterFailedAcquire 和 parkAndCheckInterrupt 都做了哪些事:

/**
 * 判斷當前線程獲取鎖失敗之后是否需要掛起.
 */
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //前驅(qū)節(jié)點的狀態(tài)
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        // 前驅(qū)節(jié)點狀態(tài)為signal,返回true
        return true;
    // 前驅(qū)節(jié)點狀態(tài)為CANCELLED
    if (ws > 0) {
        // 從隊尾向前尋找第一個狀態(tài)不為CANCELLED的節(jié)點
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        // 將前驅(qū)節(jié)點的狀態(tài)設置為SIGNAL
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}
/**
 * 掛起當前線程,返回線程中斷狀態(tài)并重置
 */
private final boolean parkAndCheckInterrupt() {
    LockSupport.park(this);
    return Thread.interrupted();
}

通過以上代碼可以看出,線程入隊后能夠掛起的前提是,它的前驅(qū)節(jié)點的狀態(tài)為 SIGNAL,這意味著當前一個節(jié)點獲取鎖并且出隊后,需要把后面的節(jié)點進行喚醒。

加鎖說完了再說解鎖,解鎖的方法相比來說更加簡單,核心方法是 release():

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

代碼流程:先嘗試釋放鎖,若釋放成功,那么查看頭結點的狀態(tài)是否為 SIGNAL,如果是,則喚醒頭結點的下個節(jié)點關聯(lián)的線程,如果釋放失敗就返回 false 表示解鎖失敗。

其中的 tryRelease() 方法實現(xiàn)如下,詳細流程見注釋說明:

/**
 * 釋放當前線程占用的鎖
 * @param releases
 * @return 是否釋放成功
 */
protected final boolean tryRelease(int releases) {
    // 計算釋放后state值
    int c = getState() - releases;
    // 如果不是當前線程占用鎖,那么拋出異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        // 鎖被重入次數(shù)為0,表示釋放成功
        free = true;
        // 清空獨占線程
        setExclusiveOwnerThread(null);
    }
    // 更新state值
    setState(c);
    return free;
}

共享模式

共享模式和獨占模式最大的區(qū)別在于,共享模式具有傳播的特性。

共享模式獲取鎖的方法為 acquireShared,相比于獨占模式,共享模式的加鎖多了一個步驟,即自己拿到資源后,還會去喚醒后繼隊友;

而共享模式釋放鎖的方法為 releaseShared,它會釋放指定量的資源,如果成功釋放且允許喚醒等待線程,會喚醒等待隊列里的其他線程來獲取資源。

總結

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關文章

  • Java 實戰(zhàn)練手項目之酒店管理系統(tǒng)的實現(xiàn)流程

    Java 實戰(zhàn)練手項目之酒店管理系統(tǒng)的實現(xiàn)流程

    讀萬卷書不如行萬里路,只學書上的理論是遠遠不夠的,只有在實戰(zhàn)中才能獲得能力的提升,本篇文章手把手帶你用java+SSM+jsp+mysql+maven實現(xiàn)一個酒店管理系統(tǒng),大家可以在過程中查缺補漏,提升水平
    2021-11-11
  • Java list利用遍歷進行刪除操作3種方法解析

    Java list利用遍歷進行刪除操作3種方法解析

    這篇文章主要介紹了Java list利用遍歷進行刪除操作3種方法解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2020-01-01
  • SpringBoot 如何優(yōu)雅的實現(xiàn)跨服務器上傳文件的示例

    SpringBoot 如何優(yōu)雅的實現(xiàn)跨服務器上傳文件的示例

    這篇文章主要介紹了SpringBoot 如何優(yōu)雅的實現(xiàn)跨服務器上傳文件的示例,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧
    2006-11-11
  • SpringBoot中優(yōu)化if-else語句的七種方法

    SpringBoot中優(yōu)化if-else語句的七種方法

    if-else語句是控制流程的基本工具,但過度使用會使代碼變得復雜且難以維護,在SpringBoot , SpringCloud項目中,優(yōu)化if-else結構變得尤為重要,本文將深入探討七種策略,旨在減少SpringBoot , SpringCloud項目中 if-else的使用,需要的朋友可以參考下
    2024-07-07
  • RabbitMQ消息拒絕如何解決

    RabbitMQ消息拒絕如何解決

    這篇文章主要介紹了RabbitMQ消息拒絕如何解決問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • Java 創(chuàng)建并應用PPT幻燈片母版的方法示例

    Java 創(chuàng)建并應用PPT幻燈片母版的方法示例

    幻燈片母版可供用戶設置幻燈片的樣式,本文將介紹如何用Java創(chuàng)建并應用單個或多個幻燈片母版。感興趣可以了解一下
    2020-06-06
  • JavaMail實現(xiàn)帶附件的郵件發(fā)送

    JavaMail實現(xiàn)帶附件的郵件發(fā)送

    這篇文章主要為大家詳細介紹了JavaMail實現(xiàn)帶附件的郵件發(fā)送,文中示例代碼介紹的非常詳細,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-08-08
  • java實現(xiàn)在pdf模板的指定位置插入圖片

    java實現(xiàn)在pdf模板的指定位置插入圖片

    這篇文章主要為大家詳細介紹了java如何實現(xiàn)在pdf模板的指定位置插入圖片,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2018-10-10
  • Java中List使用stream流轉(zhuǎn)成map的幾種方式詳解

    Java中List使用stream流轉(zhuǎn)成map的幾種方式詳解

    Stream是Java8中處理集合的關鍵抽象概念,它可以指定你希望對集合進行的操作,可以執(zhí)行非常復雜的查找、過濾和映射數(shù)據(jù)等操作,下面這篇文章主要給大家介紹了關于Java中List使用stream流轉(zhuǎn)成map的幾種方式,需要的朋友可以參考下
    2023-04-04
  • springboot2版本無法加載靜態(tài)資源問題解決

    springboot2版本無法加載靜態(tài)資源問題解決

    這篇文章主要介紹了springboot2版本無法加載靜態(tài)資源問題解決,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-11-11

最新評論

屏南县| 嘉义市| 余庆县| 股票| 平谷区| 南开区| 奇台县| 九龙县| 韶山市| 顺昌县| 嘉荫县| 克拉玛依市| 阿拉善盟| 兴隆县| 琼结县| 鹤壁市| 郓城县| 隆尧县| 万州区| 宜春市| 博乐市| 宁陵县| 宿迁市| 扎鲁特旗| 荔波县| 定远县| 江津市| SHOW| 望城县| 嘉义市| 兰溪市| 昌乐县| 皮山县| 阳信县| 兴文县| 长白| 五大连池市| 山阳县| 昌图县| 福州市| 盐源县|