ReentrantLock 源碼分析以及 AQS (一)

前言

JDK1.5 之后發(fā)布了JUC(java.util.concurrent),用于解決多線程并發(fā)問題。AQS 是一個特別重要的同步框架,很多同步類都借助于 AQS 實(shí)現(xiàn)了對線程同步狀態(tài)的管理。

AQS 中最主要的就是獨(dú)占鎖和共享鎖的獲取和釋放,以及提供了一些可中斷的獲取鎖,超時等待鎖等方法。

ReentranLock 是基于 AQS 獨(dú)占鎖的一個實(shí)現(xiàn)。ReentrantReadWriteLock 是基于 AQS 共享鎖的一個讀寫鎖實(shí)現(xiàn)。本來打算一篇文章里面寫完獨(dú)占鎖和共享鎖,但是發(fā)現(xiàn)篇幅太長了,也不易于消化。

因此,本篇就先結(jié)合 ReentrantLock 源碼,分析 AQS 的獨(dú)占鎖獲取和釋放。以及 ReentrantLock 的公平鎖和非公平鎖實(shí)現(xiàn)。

下一篇再寫 ReentrantReadWriteLock 讀寫鎖源碼,以及 AQS 共享鎖的獲取和釋放。

在正式講解源碼之前,墻裂建議讀者做一些準(zhǔn)備工作,最好對以下知識有一定的了解,這樣閱讀起來源碼會比較輕松(因?yàn)?,我?dāng)初剛開始接觸多線程時,直接看 AQS 簡直是一臉懵逼,就像讀天書一樣。。)。

  1. 了解雙向鏈表的數(shù)據(jù)結(jié)構(gòu),以及隊(duì)列的入隊(duì)出隊(duì)等操作。
  2. LockSupport 的 park,unpark 方法,以及對線程的 interrupt 幾個方法了解(可參考:LockSupport的 park 方法是怎么響應(yīng)中斷的?)。
  3. 對 CAS 和自旋機(jī)制有一定的了解。

AQS 同步隊(duì)列

AQS 內(nèi)部維護(hù)了一個 FIFO(先進(jìn)先出)的雙向隊(duì)列。它的內(nèi)部是用雙向鏈表來實(shí)現(xiàn)的,每個數(shù)據(jù)節(jié)點(diǎn)(Node)中都包含了當(dāng)前節(jié)點(diǎn)的線程信息,還有它的前后兩個指針,分別指向前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn)。下邊看一下 Node 的屬性和方法:

static final class Node {
    //可以認(rèn)為是一種標(biāo)記,表明了這個 node 是以共享模式在同步隊(duì)列中等待
    static final Node SHARED = new Node();
    //也是一種標(biāo)記,表明這個 node 是以獨(dú)占模式在同步隊(duì)列中等待
    static final Node EXCLUSIVE = null;

    /** waitStatus 常量值 */
    //說明當(dāng)前節(jié)點(diǎn)被取消,原因有可能是超時,或者被中斷。
    //節(jié)點(diǎn)被取消的狀態(tài)是不可逆的,也就是說此節(jié)點(diǎn)會一直停留在取消狀態(tài),不會轉(zhuǎn)變。
    static final int CANCELLED =  1;
    //說明后繼節(jié)點(diǎn)的線程被 park 阻塞,因此當(dāng)前線程需要在釋放鎖或者被取消時,喚醒后繼節(jié)點(diǎn)
    static final int SIGNAL    = -1;
    //說明線程在 condition 條件隊(duì)列等待
    static final int CONDITION = -2;
    //在共享模式中用,表明下一個共享線程應(yīng)該無條件傳播
    static final int PROPAGATE = -3;

    
    //當(dāng)前線程的等待狀態(tài),除了以上四種值,還有一個值 0 為初始化狀態(tài)(條件隊(duì)列的節(jié)點(diǎn)除外)。
    //注意這個值修改時是通過 CAS ,以保證線程安全。
    volatile int waitStatus;

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

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

    //當(dāng)前節(jié)點(diǎn)中的線程,通過構(gòu)造函數(shù)初始化,出隊(duì)時會置空(這個后續(xù)說,重點(diǎn)強(qiáng)調(diào))
    volatile Thread thread;

    //有兩種情況。1.在 condition 條件隊(duì)列中的后一個節(jié)點(diǎn) 
    //2. 一個特殊值 SHARED 用于表明當(dāng)前是共享模式(因?yàn)闂l件隊(duì)列只存在于獨(dú)占模式)
    Node nextWaiter;

    //是否是共享模式,理由同上
    final boolean isShared() {
        return nextWaiter == SHARED;
    }

    //返回前驅(qū)節(jié)點(diǎn),如果為空拋出空指針
    final Node predecessor() throws NullPointerException {
        Node p = prev;
        if (p == null)
            throw new NullPointerException();
        else
            return p;
    }

    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;
    }
}

另外,在 AQS 類中,還會記錄同步隊(duì)列的頭結(jié)點(diǎn)和尾結(jié)點(diǎn):

    //同步隊(duì)列的頭結(jié)點(diǎn),是懶加載的,即不會立即創(chuàng)建一個同步隊(duì)列,
    //只有當(dāng)某個線程獲取不到鎖,需要排隊(duì)的時候,才會初始化頭結(jié)點(diǎn)
    private transient volatile Node head;

    //同步隊(duì)列的尾結(jié)點(diǎn),同樣是懶加載。
    private transient volatile Node tail;

獨(dú)占鎖

這部分就結(jié)合 ReentrantLock 源碼分析 AQS 的獨(dú)占鎖是怎樣獲得和釋放鎖的。

非公平鎖

首先,我們從 ReentrantLock 開始分析,它有兩個構(gòu)造方法,一個構(gòu)造,可以傳入一個 boolean 類型的參數(shù),表明是用公平鎖還是非公平鎖模式。另一個構(gòu)造方法,不傳入任何參數(shù),則默認(rèn)用非公平鎖。

public ReentrantLock() {
    sync = new NonfairSync();
}

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

NonfairSync 和 FairSync 都繼承自 Sync ,它們都是 ReentranLock 的內(nèi)部類。 而Sync 類又繼承自 AQS (AbstractQueuedSynchronizer)。

static final class NonfairSync extends Sync {
}

static final class FairSync extends Sync {
}

abstract static class Sync extends AbstractQueuedSynchronizer {
}

知道了它們之間的繼承關(guān)系,我們就從非公平鎖的加鎖方法作為入口,跟蹤源碼。因?yàn)榉枪芥i的流程講明白之后,公平鎖大致流程都一樣,只是多了一個條件判斷(這個,一會兒后邊細(xì)講,會做對比)。

NonfairSync.lock

我們看下公平鎖的獲取鎖的方法:

final void lock() {
    //通過 CAS 操作把 state 設(shè)置為 1
    if (compareAndSetState(0, 1))
        //如果設(shè)值成功,說明加鎖成功,保存當(dāng)前獲得鎖的線程
        setExclusiveOwnerThread(Thread.currentThread());
    else
        //如果加鎖失敗,則執(zhí)行 AQS 的acquire 方法
        acquire(1);
}

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

acquire

這個方法的邏輯是:

  1. 通過 tryAcquire 方法,嘗試獲取鎖,如果成功,則返回 true,失敗返回 false 。
  2. tryAcquire 失敗之后,會先調(diào)用 addWaiter 方法,把當(dāng)前線程封裝成 node 節(jié)點(diǎn),加入同步隊(duì)列(獨(dú)占模式)。
  3. acquireQueued 方法會把剛加入隊(duì)列的 node 作為參數(shù),通過自旋去獲得鎖。

tryAcquire

這是一個模板方法,具體的實(shí)現(xiàn)需要看它的子類,這里對應(yīng)的就是 ReentrantLock.NonfairSync.tryAcquire 方法。我們看一下:

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

final boolean nonfairTryAcquire(int acquires) {
    //當(dāng)前線程
    final Thread current = Thread.currentThread();
    //獲取當(dāng)前的同步狀態(tài),若為 0 ,表示無鎖狀態(tài)。若大于 0,表示已經(jīng)有線程搶到了鎖。
    int c = getState();
    if (c == 0) {
        //然后通過 CAS 操作把 state 的值改為 1。
        if (compareAndSetState(0, acquires)) {
            // CAS 成功之后,保存當(dāng)前獲得鎖的線程
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    // 如果 state 大于0,則判斷當(dāng)前線程是否是獲得鎖的線程,是的話,可重入。
    else if (current == getExclusiveOwnerThread()) {
        //由于 ReentrantLock 是可重入的,所以每重入一次 state 就加 1 。
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

addWaiter

如果獲取鎖失敗之后,就會調(diào)用 addWaiter 方法,把當(dāng)前線程加入同步隊(duì)列。

private Node addWaiter(Node mode) {
    //把當(dāng)前線程封裝成 Node ,并且是獨(dú)占模式
    Node node = new Node(Thread.currentThread(), mode);
    //嘗試快速入隊(duì),如果失敗,則會調(diào)用 enq 入隊(duì)方法。enq 會初始化隊(duì)列。
    Node pred = tail;
    //如果 tail 不為空,說明當(dāng)前隊(duì)列中已經(jīng)有節(jié)點(diǎn)
    if (pred != null) { 
        //把當(dāng)前 node 的 prev 指針指向 tail
        node.prev = pred;
        //通過 CAS 把 node 設(shè)置為 tail,即添加到隊(duì)尾
        if (compareAndSetTail(pred, node)) {
            //把舊的 tail 節(jié)點(diǎn)的 next 指針指向當(dāng)前 node
            pred.next = node;
            return node;
        }
    }
    //當(dāng) tail 為空時,把 node 添加到隊(duì)列,如果需要的話,先進(jìn)行隊(duì)列初始化
    enq(node);
    //入隊(duì)成功之后,返回當(dāng)前 node
    return node;
}

enq

通過自旋,把當(dāng)前節(jié)點(diǎn)加入到隊(duì)列中

private Node enq(final Node node) {
    for (;;) {
        Node t = tail;
        //如果 tail為空,說明隊(duì)列未初始化
        if (t == null) { 
            //創(chuàng)建一個空節(jié)點(diǎn),通過 CAS把它設(shè)置為頭結(jié)點(diǎn)
            if (compareAndSetHead(new Node()))
                //此時只有一個 head頭節(jié)點(diǎn),因此把 tail也指向它
                tail = head;
        } else {
            //第二次自旋時,tail不為空,于是把當(dāng)前節(jié)點(diǎn)的 prev指向 tail節(jié)點(diǎn)
            node.prev = t;
            //通過 CAS把 tail節(jié)點(diǎn)設(shè)置為當(dāng)前 node節(jié)點(diǎn)
            if (compareAndSetTail(t, node)) {
                //把舊的 tail節(jié)點(diǎn)的 next指向當(dāng)前 node
                t.next = node;
                return t;
            }
        }
    }
}

acquireQueued

入隊(duì)成功之后,就會調(diào)用 acquireQueued 方法自旋搶鎖。

final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            //獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
            final Node p = node.predecessor();
            //如果前驅(qū)節(jié)點(diǎn)就是 head 節(jié)點(diǎn),就調(diào)用 tryAcquire 方法搶鎖
            if (p == head && tryAcquire(arg)) {
                //如果搶鎖成功,就把當(dāng)前 node 設(shè)置為頭結(jié)點(diǎn)
                setHead(node);
                p.next = null; // help GC
                failed = false;
                //搶鎖成功后,會把線程中斷標(biāo)志返回出去,終止for循環(huán)
                return interrupted;
            }
            //如果搶鎖失敗,就根據(jù)前驅(qū)節(jié)點(diǎn)的 waitStatus 狀態(tài)判斷是否需要把當(dāng)前線程掛起
            if (shouldParkAfterFailedAcquire(p, node) &&
                //線程被掛起時,判斷是否被中斷過
                parkAndCheckInterrupt())
                //注意此處,如果被線程被中斷過,需要把中斷標(biāo)志重新設(shè)置一下
                interrupted = true;
        }
    } finally {
        if (failed)
            //如果拋出異常,則取消鎖的獲取,進(jìn)行出隊(duì)操作
            cancelAcquire(node);
    }
}

setHead

通過代碼,我們可以看到,當(dāng)前的同步隊(duì)列中,只有第二個節(jié)點(diǎn)才有資格搶鎖。如果搶鎖成功,則會把它設(shè)置為頭結(jié)點(diǎn)。

private void setHead(Node node) {
    head = node;
    node.thread = null;
    node.prev = null;
}

需要注意的是,這個方法,會把頭結(jié)點(diǎn)的線程設(shè)置為 null 。想一下,為什么?

因?yàn)?,此時頭結(jié)點(diǎn)的線程已經(jīng)搶鎖成功,需要出隊(duì)了。自然的,隊(duì)列中也就不應(yīng)該存在這個線程了。

PS:由 enq 方法,還有 setHead 方法,我們可以發(fā)現(xiàn),頭結(jié)點(diǎn)的線程總是為 null。這是因?yàn)椋^結(jié)點(diǎn)要么是剛初始化的空節(jié)點(diǎn),要么是搶到鎖的線程出隊(duì)了。因此,我們也常常把頭結(jié)點(diǎn)叫做虛擬節(jié)點(diǎn)(不存儲任何線程)。

shouldParkAfterFailedAcquire

以上是搶鎖成功的情況,那么搶鎖失敗了呢?這時,我們需要判斷是否應(yīng)該把當(dāng)前線程掛起。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
    //獲取當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的 waitStatus
    int ws = pred.waitStatus;
    if (ws == Node.SIGNAL)
        //如果 ws = -1 ,說明當(dāng)前線程可以被前驅(qū)節(jié)點(diǎn)正常喚醒,于是就可以安全的 park了
        return true;
    if (ws > 0) {
        //如果 ws > 0,說明前驅(qū)節(jié)點(diǎn)被取消,則會從當(dāng)前節(jié)點(diǎn)依次向前查找,
        //直到找到第一個沒有被取消的節(jié)點(diǎn),把那個節(jié)點(diǎn)的 next 指向當(dāng)前 node
        //這一步,是為了找到一個可以把當(dāng)前線程喚起的前驅(qū)節(jié)點(diǎn)
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        //如果 ws 為 0,或者 -3(共享鎖狀態(tài)),則把它設(shè)置為 -1 
        //返回 false,下次自旋時,就會判斷等于 -1,返回 true了
        compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
    }
    return false;
}   

parkAndCheckInterrupt

如果 shouldParkAfterFailedAcquire 返回 true,說明當(dāng)前線程需要被掛起。因此,就執(zhí)行此方法,同時檢查線程是否被中斷。

private final boolean parkAndCheckInterrupt() {
    //把當(dāng)前線程掛起,則 acquireQueued 方法的自旋就會暫停,等待前驅(qū)節(jié)點(diǎn) unpark
    LockSupport.park(this);
    //返回當(dāng)前節(jié)點(diǎn)是否被中斷的標(biāo)志,注意此方法會把線程的中斷標(biāo)志清除。
    //因此,返回上一層方法時,需要設(shè)置 interrupted = true 把中斷標(biāo)志重新設(shè)置,以便上層代碼可以處理中斷
    return Thread.interrupted();
}

想一下,為什么搶鎖失敗后,需要判斷是否把線程掛起?

因?yàn)?,如果搶不到鎖,并且還不把線程掛起,acquireQueued 方法就會一直自旋下去,這樣你的CPU能受得了嗎。

cancelAcquire

當(dāng)不停的自旋搶鎖時,若發(fā)生了異常,就會調(diào)用此方法,取消正在嘗試獲取鎖的線程。node 的位置分為三種情況,見下面注釋,

private void cancelAcquire(Node node) {

    if (node == null)
        return;

    // node 不再指向任何線程
    node.thread = null;

    Node pred = node.prev;
    //從當(dāng)前節(jié)點(diǎn)不斷的向前查找,直到找到一個有效的前驅(qū)節(jié)點(diǎn)
    while (pred.waitStatus > 0)
        node.prev = pred = pred.prev;

    Node predNext = pred.next;

    //把 node 的 ws 設(shè)置為 -1 
    node.waitStatus = Node.CANCELLED;

    // 1.如果 node 是 tail,則把 tail 更新為 node,并把 pred.next 指向 null
    if (node == tail && compareAndSetTail(node, pred)) {
        compareAndSetNext(pred, predNext, null);
    } else {
        int ws;
        //2.如果 node 既不是 tail,也不是 head 的后繼節(jié)點(diǎn),就把 node的前驅(qū)節(jié)點(diǎn)的 ws 設(shè)置為 -1
        //最后把 node 的前驅(qū)節(jié)點(diǎn)的 next 指向 node 的后繼節(jié)點(diǎn)
        if (pred != head &&
            ((ws = pred.waitStatus) == Node.SIGNAL ||
             (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
            pred.thread != null) {
            Node next = node.next;
            if (next != null && next.waitStatus <= 0)
                compareAndSetNext(pred, predNext, next);
        } else {
            //3.如果 node是 head 的后繼節(jié)點(diǎn),則直接喚醒 node 的后繼節(jié)點(diǎn)。
            //這個也很好理解,因?yàn)?node 是隊(duì)列中唯一有資格嘗試獲取鎖的節(jié)點(diǎn),
            //它放棄了資格,當(dāng)然有義務(wù)把后繼節(jié)點(diǎn)喚醒,以讓后繼節(jié)點(diǎn)嘗試搶鎖。
            unparkSuccessor(node);
        }

        node.next = node; // help GC
    }
}

unparkSuccessor

這個喚醒方法就比較簡單了,

private void unparkSuccessor(Node node) {
    
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    Node s = node.next;
    if (s == null || s.waitStatus > 0) {
        s = null;
        //從尾結(jié)點(diǎn)向前依次遍歷,直到找到距離當(dāng)前 node 最近的一個有效節(jié)點(diǎn)
        for (Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        //把這個有效節(jié)點(diǎn)的線程喚醒,
        //喚醒之后,當(dāng)前線程就可以繼續(xù)自旋搶鎖了,(回到 park 的地方)
        LockSupport.unpark(s.thread);
}

下面畫一個流程圖更直觀的查看整個獲取鎖的過程。

公平鎖

公平鎖和非公平鎖的整體流程大致相同,只是在搶鎖之前先判斷一下是否已經(jīng)有人排在前面,如果有的話,就不執(zhí)行搶鎖。我們通過源碼追蹤到 FairSync.tryAcquire 方法。會發(fā)現(xiàn),多了一個 hasQueuedPredecessors 方法。

hasQueuedPredecessors

這個方法判斷邏輯稍微有點(diǎn)復(fù)雜,有多種情況。

public final boolean hasQueuedPredecessors() {
    Node t = tail; 
    Node h = head;
    Node s;
    return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
}
  1. 如果 h == t,說明 h 和 t 都為空(此時隊(duì)列還未初始化)或者它們是同一個節(jié)點(diǎn)(說明隊(duì)列已經(jīng)初始化,并且只有一個節(jié)點(diǎn),此時為 enq 方法第一次自旋成功后)。此時,返回false。
  2. 如果 h != t,則判斷 head.next == null 是否成立,如果成立,則返回 true。這種情況發(fā)生在有其他線程第一次入隊(duì)時。在 AQS 的 enq 入隊(duì)方法,設(shè)置頭結(jié)點(diǎn)成功之后 compareAndSetHead(new Node()) ,還未執(zhí)行 tail = head 時(仔細(xì)想一想為什么?)。此時 tail = null , head = new Node(),head.next = null。
  3. 如果 h != t,并且 head.next != null,說明此時隊(duì)列中至少已經(jīng)有兩個節(jié)點(diǎn),則判斷 head.next 是否是當(dāng)前線程。如果是,返回 false(注意是 false哦,因?yàn)橛昧?!),否則返回 true 。

總結(jié):以上幾種情況,只有最終返回 false 時,才會繼續(xù)往下執(zhí)行。因?yàn)?false,說明沒有線程排在當(dāng)前線程前面,于是通過 CAS 嘗試把 state 值設(shè)置為 1。若成功,則方法返回。若失敗,同樣需要去排隊(duì)。

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

舉個例子來對比公平鎖和非公平鎖。比如,現(xiàn)在到飯點(diǎn)了,大家都到食堂打飯。把隊(duì)列中的節(jié)點(diǎn)比作排隊(duì)打飯的人,每個打飯窗口都有一個管理員,只有排隊(duì)的人從管理員手中搶到鎖,才有資格打飯。打飯的過程就是線程執(zhí)行的過程。

如果,你發(fā)現(xiàn)前面沒有人在排隊(duì),那么就可以直接從管理員手中拿到鎖,然后打飯。對于公平鎖來說,如果你前面有人在打飯,那么你就要排隊(duì)到他后面(圖中B),等他打完之后,把鎖還給管理員。那么,你就可以從管理員手中拿到鎖,然后打飯了。后面的人依次排隊(duì)。這就是FIFO先進(jìn)先出的隊(duì)列模型。

對于非公平鎖來說,如果你是圖中的 B,當(dāng) A 把鎖還給管理員后,有可能有另外一個 D 插隊(duì)過來直接把鎖搶走。那么,他就可以打飯,你只能繼續(xù)等待了。

所以,可以看出來。公平鎖是嚴(yán)格按照排隊(duì)的順序來的,先來后到嘛,你來的早,就可以早點(diǎn)獲取鎖。優(yōu)點(diǎn)是,這樣不會造成某個線程等待時間過長,因?yàn)榇蠹叶际侵幸?guī)中矩的在排隊(duì)。而缺點(diǎn)呢,就是會頻繁的喚起線程,增加 CPU的開銷。

非公平鎖的優(yōu)點(diǎn)是吞吐量大,因?yàn)橛锌赡苷面i可用,然后線程來了,直接搶到鎖了,不用排隊(duì)了,這樣也減少了 CPU 喚醒排隊(duì)線程的開銷。 但是,缺點(diǎn)也很明顯,你說我排隊(duì)排了好長時間了,終于輪到我打飯了,憑什么其他人剛過來就插到我前面,比我還先打到飯,也太不公平了吧,后邊一大堆排隊(duì)的人更是怨聲載道。這要是每個人來了都插到我前面去,我豈不是要餓死了。

獨(dú)占鎖的釋放

我們從 ReentrantLock 的 unlock 方法看起:

public void unlock() {
    //調(diào)用 AQS 的 release 方法
    sync.release(1);
}

public final boolean release(int arg) {
    if (tryRelease(arg)) {
        Node h = head;
        //如果頭結(jié)點(diǎn)不為空,并且 ws 不為 0,則喚起后繼節(jié)點(diǎn)
        if (h != null && h.waitStatus != 0)
            unparkSuccessor(h);
        return true;
    }
    return false;
}

這段邏輯比較簡單,當(dāng)線程釋放鎖之后,就會喚醒后繼節(jié)點(diǎn)。 unparkSuccessor 已講,不再贅述。然后看下 tryRelease 方法,公平鎖和非公平鎖走的是同一個方法。

protected final boolean tryRelease(int releases) {
    //每釋放一次鎖,state 值就會減 1,因?yàn)橹翱赡苡墟i的重入
    int c = getState() - releases;
    //如果當(dāng)前線程不是搶到鎖的線程,則拋出異常
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        //只有 state 的值減到 0 的時候,才會全部釋放鎖
        free = true;
        setExclusiveOwnerThread(null);
    }
    setState(c);
    return free;
}

因?yàn)?,ReentrantLock 支持鎖的重入,所以每次重入 state 值都會加 1,相應(yīng)的每次釋放鎖, state 的值也會減 1 。所以,這也是為什么每個 lock 方法最后都要有一個 unlock 方法釋放鎖,它們的個數(shù)需要保證相同。

當(dāng) state 值為 0 的時候,說明鎖完全釋放。其他線程才可以有機(jī)會搶到鎖。

結(jié)語

以上已經(jīng)講解了獨(dú)占鎖主要的獲取方法 acquire ,另外還有一些其他相關(guān)方法,不再贅述,因?yàn)橹饕壿嫸际且粯拥模挥胁糠稚杂胁煌?,只要理解?acquire ,這些都是相通的。如 acquireInterruptibly 方法,它可以在獲取鎖的時候響應(yīng)中斷。還有超時獲取鎖的方法 doAcquireNanos 可以設(shè)定獲取鎖的超時時間,超時之后就返回失敗。

下篇預(yù)告:分析 ReentrantReadWriteLock 讀寫鎖源碼,以及 AQS 共享鎖的獲取和釋放,敬請期待。

如果本文對你有用,歡迎點(diǎn)贊,評論,轉(zhuǎn)發(fā)。

學(xué)習(xí)是枯燥的,也是有趣的。我是「煙雨星空」,歡迎關(guān)注,可第一時間接收文章推送。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容