java源碼 - ReentrantLock圖解加鎖過(guò)程

開(kāi)篇

用圖形化的方式加深加鎖和解鎖過(guò)程的解釋性。

java源碼 - ReentrantLock
java源碼 - ReentrantLock之FairSync
java源碼 - ReentrantLock之NonfairSync
java源碼 - ReentrantLock圖解加鎖過(guò)程

加鎖流程

  • 1、首先我們分析非公平鎖的的請(qǐng)求過(guò)程。我們假設(shè)在這個(gè)時(shí)候,還沒(méi)有任務(wù)線程獲取鎖,這個(gè)時(shí)候,第一個(gè)線程過(guò)來(lái)了(我們使用的是非公平鎖),那么第一個(gè)線程thread1會(huì)去獲取鎖.
    這時(shí)它會(huì)調(diào)用下面的方法,通過(guò)CAS的操作,將當(dāng)前AQS的state由0變成1,證明當(dāng)前thread1已經(jīng)獲取到鎖,并且將AQS的exclusiveOwnerThread設(shè)置成thread1,證明當(dāng)前持有鎖的線程是thread1。:
final void lock() {
  if (compareAndSetState(0, 1))
      setExclusiveOwnerThread(Thread.currentThread());
  else
      acquire(1);
}


  • 2、此時(shí)來(lái)了第二個(gè)線程thread2,并且我們假設(shè)thread1還沒(méi)有釋放鎖,因?yàn)槲覀兪褂玫氖欠枪芥i,那么thread2首先會(huì)進(jìn)行搶占式的去獲取鎖,調(diào)用NonFairSync.lock方法獲取鎖。
    NonFairSync.lock方法的第一個(gè)分支是通過(guò)CAS操作獲取鎖,很明顯,這一步肯定會(huì)失敗,因?yàn)榇藭r(shí)thread1還沒(méi)有釋放鎖。那么thread2將會(huì)走NonFairSync.lock方法的第二個(gè)分支,進(jìn)行acquire(1)操作。
    acquire(1)其實(shí)是AQS的方法,acquire(1)方法內(nèi)部首先調(diào)用tryAcquire方法,ReentrantLock.NonFairLock重寫了tryAcquire方法,并且ReentrantLock.NonFairLock的tryAcquire方法又調(diào)用了ReentrantLock.Sync的nonfairTryAcquire方法.
    nonfairTryAcquire方法如下:
final boolean nonfairTryAcquire(int acquires) {
          final Thread current = Thread.currentThread();
          int c = getState();
          if (c == 0) {
              if (compareAndSetState(0, acquires)) {
                  setExclusiveOwnerThread(current);
                  return true;
              }
          }
          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;
      }

這個(gè)方法的執(zhí)行邏輯如下:

  • 1. 獲取當(dāng)前將要去獲取鎖的線程,在此時(shí)的情況下,也就是我們的thread2線程。

  • 2. 獲取當(dāng)前AQS的state的值。如果此時(shí)state的值是0,那么我們就通過(guò)CAS操作獲取鎖,然后設(shè)置AQS的exclusiveOwnerThread為thread2。很明顯,在當(dāng)前的這個(gè)執(zhí)行情況下,state的值是1不是0,因?yàn)槲覀兊膖hread1還沒(méi)有釋放鎖。

  • 3. 如果當(dāng)前將要去獲取鎖的線程等于此時(shí)AQS的exclusiveOwnerThread的線程,則此時(shí)將state的值加1,很明顯這是重入鎖的實(shí)現(xiàn)方式。在此時(shí)的運(yùn)行狀態(tài)下,將要去獲取鎖的線程不是thread1,也就是說(shuō)這一步不成立。

  • 4. 以上操作都不成立的話,我們直接返回false。

既然返回了false,那么之后就會(huì)調(diào)用addWaiter方法,這個(gè)方法負(fù)責(zé)把當(dāng)前無(wú)法獲取鎖的線程包裝為一個(gè)Node添加到隊(duì)尾。通過(guò)下面的代碼片段我們就知道調(diào)用邏輯:

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

我們進(jìn)入到addWaiter方法內(nèi)部去看:

    private Node addWaiter(Node mode) {
        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內(nèi)部:

第一步:將當(dāng)前將要去獲取鎖的線程也就是thread2和獨(dú)占模式封裝為一個(gè)node對(duì)象。并且我們也知道在當(dāng)前的執(zhí)行環(huán)境下,線程阻塞隊(duì)列是空的,因?yàn)閠hread1獲取了鎖,thread2也是剛剛來(lái)請(qǐng)求鎖,所以線程阻塞隊(duì)列里面是空的。很明顯,這個(gè)時(shí)候隊(duì)列的尾部tail節(jié)點(diǎn)也是null,那么將直接進(jìn)入到enq方法。

第二步:我們首先看下enq方法的內(nèi)部實(shí)現(xiàn)。首先內(nèi)部是一個(gè)自懸循環(huán)。

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



第一次循環(huán):
t為null,隨后我們new出了一個(gè)空的node節(jié)點(diǎn),并且通過(guò)CAS操作設(shè)置了線程的阻塞隊(duì)列的head節(jié)點(diǎn)就是我們剛才new出來(lái)的那個(gè)空的node節(jié)點(diǎn),其實(shí)這是一個(gè)“假節(jié)點(diǎn)”,那么什么是“假節(jié)點(diǎn)”呢?那就是節(jié)點(diǎn)中不包含線程。設(shè)置完head節(jié)點(diǎn)后,同時(shí)又將head節(jié)點(diǎn)賦值給尾部tail節(jié)點(diǎn),到此第一次循環(huán)結(jié)束。此時(shí)的節(jié)點(diǎn)就是如下:



第二次循環(huán):
現(xiàn)在判斷尾部tail已經(jīng)不是null了,那么就走第二個(gè)分支了。將尾部tail節(jié)點(diǎn)賦值給我們傳遞進(jìn)來(lái)的節(jié)點(diǎn)Node的前驅(qū)節(jié)點(diǎn),此時(shí)的結(jié)構(gòu)如下:



然后再通過(guò)CAS的操作,將我們傳遞進(jìn)來(lái)的節(jié)點(diǎn)node設(shè)置成尾部tail節(jié)點(diǎn),并且將我們的node節(jié)點(diǎn)賦值給原來(lái)的老的那個(gè)尾部節(jié)點(diǎn)的后繼節(jié)點(diǎn),此時(shí)的結(jié)構(gòu)如下:

這個(gè)時(shí)候代碼中使用了return關(guān)鍵字,也就是證明我們經(jīng)過(guò)了2次循環(huán)跳出了這個(gè)自懸循環(huán)體系。

按照代碼的執(zhí)行流程,接下來(lái)將會(huì)調(diào)用acquireQueued方法,主要是判斷當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是不是head節(jié)點(diǎn),如果是的話,就再去嘗試獲取鎖,如果不是,就掛起當(dāng)前線程。這里可能有人疑問(wèn)了,為什么判斷當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是head節(jié)點(diǎn)的話就去嘗試獲取鎖呢?因?yàn)槲覀冎纇ead節(jié)點(diǎn)是一個(gè)假節(jié)點(diǎn),如果當(dāng)前的節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)即是假節(jié)點(diǎn)的話,那么這個(gè)假節(jié)點(diǎn)的后繼節(jié)點(diǎn)就有可能有獲取鎖的機(jī)會(huì),所以我們需要去嘗試。

現(xiàn)在我們看下acquireQueued方法內(nèi)部,我們也可以清楚的看到,這個(gè)方法的內(nèi)部也是一個(gè)自懸循環(huán)。

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



第一次循環(huán):獲取我們傳入node的前驅(qū)節(jié)點(diǎn),判斷是否是head節(jié)點(diǎn),現(xiàn)在我們的狀態(tài)是:

很明顯滿足當(dāng)前node節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是head節(jié)點(diǎn),那么現(xiàn)在我們就要去調(diào)用tryAcquire方法,也就是NonfairSync類的tryAcquire方法,而這個(gè)方法又調(diào)用了ReentrantLock.Sync.nonfairTryAcquire方法。

很明顯此時(shí)thread2獲取鎖是失敗的,直接返回false。按照調(diào)用流程,現(xiàn)在進(jìn)入了當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的shouldParkAfterFailedAcquire方法,檢查當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的waitstatus。shouldParkAfterFailedAcquire方法內(nèi)部如下:

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
  • 1. 如果前驅(qū)節(jié)點(diǎn)的waitStatus為-1,也就是SIGNAL,就返回true。
  • 2. 如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的waitstatus大于0,也就是說(shuō)被CANCEL掉了,這個(gè)時(shí)候我們會(huì)除掉這個(gè)節(jié)點(diǎn)。
  • 3. 如果都不是以上的情況,就通過(guò)CAS操作將這個(gè)前驅(qū)節(jié)點(diǎn)設(shè)置成SIGHNAL。

很明顯,我們?cè)谶@里的情況是第3種情況,并且這個(gè)方法運(yùn)行后返回false。此時(shí)的結(jié)構(gòu)如下,主要是head節(jié)點(diǎn)的waitStatus由0變成了-1。

第二次循環(huán):獲取我們傳入node的前驅(qū)節(jié)點(diǎn),判斷是否是head節(jié)點(diǎn),現(xiàn)在我們的狀態(tài)是:

很明顯滿足當(dāng)前node節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是head節(jié)點(diǎn),那么現(xiàn)在我們就要去調(diào)用tryAcquire方法,也就是NonfairSync類的tryAcquire方法,而這個(gè)方法又調(diào)用了ReentrantLock.Sync.nonfairTryAcquire方法。

很明顯此時(shí)thread2獲取鎖是失敗的,直接返回false。按照調(diào)用流程,現(xiàn)在進(jìn)入了當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的shouldParkAfterFailedAcquire方法,檢查當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的waitstatus。此時(shí)waitstatus為-1,這個(gè)方法返回true。

shouldParkAfterFailedAcquire返回true后,就會(huì)調(diào)用parkAndCheckInterrupt方法,直接將當(dāng)前線程thread2阻塞。

仔細(xì)看這個(gè)方法acquireQueued方法,是無(wú)限循環(huán),感覺(jué)如果p == head && tryAcquire(arg)條件不滿足循環(huán)將永遠(yuǎn)無(wú)法結(jié)束,在這里,當(dāng)然不會(huì)出現(xiàn)死循環(huán)。因?yàn)閜arkAndCheckInterrupt會(huì)把當(dāng)前線程阻塞。分析到這里,我們的thread2線程已經(jīng)被阻塞了,這個(gè)線程不會(huì)再繼續(xù)執(zhí)行下去了。


  • 3、假設(shè)現(xiàn)在我們的thread1還沒(méi)有釋放鎖,而現(xiàn)在又來(lái)了一個(gè)線程thread3。

thread3首先調(diào)用lock方法獲取鎖,首先去搶占鎖,因?yàn)槲覀冎纓hread1還沒(méi)有釋放鎖,這個(gè)時(shí)候thread3肯定搶占失敗,于是又調(diào)用了acquire方法,接著又失敗。接著會(huì)去調(diào)用addWaiter方法,將當(dāng)前線程thread3封裝成node加入到線程阻塞隊(duì)列的尾部?,F(xiàn)在的結(jié)構(gòu)如下:

addWaiter如下:

    private Node addWaiter(Node mode) {
        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;
    }



第一步:將當(dāng)前將要去獲取鎖的線程也就是thread3和獨(dú)占模式封裝為一個(gè)node對(duì)象。并且我們也知道在當(dāng)前的執(zhí)行環(huán)境下,線程阻塞隊(duì)列不是空的,因?yàn)閠hread2獲取了鎖,thread2已經(jīng)加入了隊(duì)列。
很明顯,這個(gè)時(shí)候隊(duì)列的尾部tail節(jié)點(diǎn)也不是null,那么將直接進(jìn)入到if分支。將尾部tail節(jié)點(diǎn)賦值給我們傳入的node節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)。如下:



第二步:通過(guò)CAS將我們傳遞進(jìn)來(lái)的node節(jié)點(diǎn)設(shè)置成tail節(jié)點(diǎn),并且將新tail節(jié)點(diǎn)設(shè)置成老tail節(jié)點(diǎn)的后繼節(jié)點(diǎn)。

到此,addWaiter方法執(zhí)行完畢,接著執(zhí)行acquireQueued方法。這是一個(gè)自循環(huán)方法。

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



第一次循環(huán):獲取我們傳入node的前驅(qū)節(jié)點(diǎn),判斷是否是head節(jié)點(diǎn),現(xiàn)在我們的狀態(tài)是:

我們傳入node的前驅(qū)節(jié)點(diǎn)不是head節(jié)點(diǎn),那么直接走第二個(gè)if分支,調(diào)用shouldParkAfterFailedAcquire方法。

    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
  • 1. 如果前驅(qū)節(jié)點(diǎn)的waitStatus為-1,也就是SIGNAL,就返回true。
  • 2. 如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的waitstatus大于0,也就是說(shuō)被CANCEL掉了,這個(gè)時(shí)候我們會(huì)除掉這個(gè)節(jié)點(diǎn)。
  • 3. 如果都不是以上的情況,就通過(guò)CAS操作將這個(gè)前驅(qū)節(jié)點(diǎn)設(shè)置成SIGHNAL。

很明顯,我們?cè)谶@里的情況是第3種情況,并且這個(gè)方法運(yùn)行后返回false。
此時(shí)的結(jié)構(gòu)如下,主要是t節(jié)點(diǎn)的waitStatus由0變成了-1。




第二次循環(huán):獲取我們傳入node的前驅(qū)節(jié)點(diǎn),判斷是否是head節(jié)點(diǎn),現(xiàn)在我們的狀態(tài)是:

很明顯我們傳入node的前驅(qū)節(jié)點(diǎn)不是head節(jié)點(diǎn),那么直接進(jìn)入shouldParkAfterFailedAcquire方法。

1. 如果前驅(qū)節(jié)點(diǎn)的waitStatus為-1,也就是SIGNAL,就返回true。
2. 如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的waitstatus大于0,也就是說(shuō)被CANCEL掉了,這個(gè)時(shí)候我們會(huì)除掉這個(gè)節(jié)點(diǎn)。
3. 如果都不是以上的情況,就通過(guò)CAS操作將這個(gè)前驅(qū)節(jié)點(diǎn)設(shè)置成SIGHNAL。

很明顯,我們?cè)谶@里的情況是第1種情況,并且這個(gè)方法運(yùn)行后返回true。
然后就會(huì)調(diào)用parkAndCheckInterrupt方法,直接將當(dāng)前線程thread3阻塞?,F(xiàn)在thread2和thread3都已經(jīng)被阻塞。

釋放鎖的過(guò)程

現(xiàn)在thread1要開(kāi)始釋放鎖了。調(diào)用unlock方法,unlock方法又調(diào)用了內(nèi)部的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;
    }
  • 獲取當(dāng)前AQS的state,并減去1,判斷當(dāng)前線程是否等于AQS的exclusiveOwnerThread,如果不是,就拋異常,這就保證了加鎖和釋放鎖必須是同一個(gè)線程。
  • 如果(state-1)的結(jié)果不為0,說(shuō)明鎖被重入了,需要多次unlock。
    如果(state-1)等于0,我們就將AQS的ExclusiveOwnerThread設(shè)置為null。
  • 如果上述操作成功了,也就是tryRelase方法返回了true,那么就會(huì)判斷當(dāng)前隊(duì)列中的head節(jié)點(diǎn),當(dāng)前結(jié)構(gòu)如下:


如果head節(jié)點(diǎn)不為null,并且head節(jié)點(diǎn)的waitStatus不為0, 我們就調(diào)用unparkSuccessor方法去喚醒head節(jié)點(diǎn)的后繼節(jié)點(diǎn)。

    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;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }



第一步:獲取head節(jié)點(diǎn)的waitStatus,如果小于0,就通過(guò)CAS操作將head節(jié)點(diǎn)的waitStatus修改為0,現(xiàn)在是:



第二步:尋找head節(jié)點(diǎn)的下一個(gè)節(jié)點(diǎn),如果這個(gè)節(jié)點(diǎn)的waitStatus小于0,就喚醒這個(gè)節(jié)點(diǎn),否則遍歷下去,找到第一個(gè)waitStatus<=0的節(jié)點(diǎn),并喚醒。現(xiàn)在thread2線程被喚醒了,我們知道剛才thread2在acquireQueued被中斷,現(xiàn)在繼續(xù)執(zhí)行,又進(jìn)入了for循環(huán),當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是head并且調(diào)用tryAquire方法獲得鎖并且成功。那么設(shè)置當(dāng)前Node為head節(jié)點(diǎn),將里面的thead和prev設(shè)置為null。

調(diào)用完畢后,acquireQueued返回false。并且現(xiàn)在thread2自由了。到此,已經(jīng)全部分析完畢。

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

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