ReentrantLock源碼分析(二)

上文簡(jiǎn)單描述了ReentrantLock中的方法大部分都是由AbstractQueuedSynchronizer或它的子類實(shí)現(xiàn),AQS隊(duì)列和條件隊(duì)列節(jié)點(diǎn)持有的信息,和它們運(yùn)行時(shí)的數(shù)據(jù)結(jié)構(gòu)是什么樣子的。

本文結(jié)合最常用的lock和unlock方法來(lái)描述AQS隊(duì)列是怎么樣構(gòu)建和運(yùn)作的。先看一個(gè)簡(jiǎn)單的demo.

public class LockDemo extends Thread {
    private Lock lock = new ReentrantLock();
    public LockDemo(Lock lock){
        this.lock = lock;
    }


    public void run() {
        try {
            this.lock.lock();
            System.out.println("我獲取到鎖了:"+Thread.currentThread().getName());
        } finally {
            this.lock.unlock();
        }
    }

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        LockDemo lockDemo1 = new LockDemo(lock);
        lockDemo1.setName("lock1");
        LockDemo lockDemo2 = new LockDemo(lock);
        lockDemo2.setName("lock2");
        LockDemo lockDemo3 = new LockDemo(lock);
        lockDemo3.setName("lock3");
        lockDemo1.start();
        lockDemo2.start();
        lockDemo3.start();
    }
}

輸出的結(jié)果如下,如我們所料,控制臺(tái)是串行輸出了結(jié)果:

我獲取到鎖了:lock1
我獲取到鎖了:lock2
我獲取到鎖了:lock3

先簡(jiǎn)單畫下lock()方法代碼流程圖。


lock.png

ok,下面開始聊實(shí)現(xiàn)細(xì)節(jié)。ReentrantLock默認(rèn)是以非公平模式構(gòu)建,后面會(huì)簡(jiǎn)單說(shuō)下公平模式和非公平模式構(gòu)建的區(qū)別。廢話 不多說(shuō),開始NonfairSync.lock()。

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

先用CAS嘗試設(shè)置鎖的狀態(tài),成功則把持有鎖的線程設(shè)置為當(dāng)前線程。否則執(zhí)行acquire(1),這里為什么是1,因?yàn)槭荝eentrantLock是可重入鎖,同一個(gè)線程每次獲取鎖都會(huì)+1。接著看aqs.accquire;

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

tryAcquire是由AQS的子類,這里也就是NonfairSync去實(shí)現(xiàn)tryAcquire。

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

第一步判斷當(dāng)前鎖是否被持有,如果沒(méi)有被持有則cas設(shè)置鎖的狀態(tài),更新持有鎖的線程。如果鎖已經(jīng)被持有,則進(jìn)行第二步判斷,如果持有鎖的線程是當(dāng)前線程,則進(jìn)行重入鎖的計(jì)算。上面兩步都沒(méi)有成功,則該線程持有鎖的動(dòng)作失敗。開始構(gòu)建AQS隊(duì)列??聪耡cquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法,先看addWaiter(Node.EXCLUSIVE)。

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

先構(gòu)建當(dāng)前線程的節(jié)點(diǎn)。模式是獨(dú)占鎖,獨(dú)占鎖的意思就是這個(gè)鎖只能被當(dāng)前線程一個(gè)線程去持有。然后判斷AQS隊(duì)列尾節(jié)點(diǎn),是不是為空,如果不為空則直接把當(dāng)前節(jié)點(diǎn)CAS操作為尾節(jié)點(diǎn)。否則執(zhí)行enq(node)方法

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

這是一個(gè)for循環(huán),退出去的條件就是當(dāng)前節(jié)點(diǎn)置位尾節(jié)點(diǎn)成功。先判斷當(dāng)前AQS隊(duì)列是否存在尾節(jié)點(diǎn)。不存在說(shuō)明隊(duì)列不存在。則CAS操作生成一個(gè)頭節(jié)點(diǎn),并且置位尾節(jié)點(diǎn)。注意該節(jié)點(diǎn)并不持有任何線程信息,就是一個(gè)空節(jié)點(diǎn)。如果尾節(jié)點(diǎn)不為空。則把當(dāng)前節(jié)點(diǎn)置位尾節(jié)點(diǎn),之前的尾節(jié)點(diǎn)的next置位當(dāng)前節(jié)點(diǎn)。從而構(gòu)造了一個(gè)雙向鏈表。構(gòu)造節(jié)點(diǎn)成功后,開始執(zhí)行acquireQueued方法。

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

這里也是一個(gè)for循環(huán),退出循環(huán)的條件為把當(dāng)前節(jié)點(diǎn)置位頭結(jié)點(diǎn)(頭結(jié)點(diǎn)不包含持有線程),首先獲取當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn),如果當(dāng)前節(jié)點(diǎn)是AQS隊(duì)列第一次構(gòu)建出來(lái)的,那么它的前置節(jié)點(diǎn)顯然就是頭節(jié)點(diǎn)。此時(shí)會(huì)去嘗試持有鎖,如果持有鎖成功了,把當(dāng)前 節(jié)點(diǎn)置位頭結(jié)點(diǎn)。并把之前的頭結(jié)點(diǎn)的next置為null,減少?gòu)?qiáng)引用方便GC。

如果當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)或者持有鎖的動(dòng)作失敗了。則執(zhí)行shouldParkAfterFailedAcquire(p, node)。

private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

第一次進(jìn)來(lái)的時(shí)候,waitStatus初始值是0,會(huì)直接走到compareAndSetWaitStatus(pred, ws, Node.SIGNAL);會(huì)把當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)的狀態(tài)設(shè)置為SIGNAL,意思需要喚醒下一個(gè)節(jié)點(diǎn)。返回false。

也就是說(shuō)會(huì)進(jìn)入第二次循環(huán),過(guò)程同上。也就是說(shuō)會(huì)第二次進(jìn)入shouldParkAfterFailedAcquire,如果waitStatus沒(méi)有被修改過(guò)則返回true。如果被修改過(guò),有可能被取消ws > 0,因?yàn)橹挥腥∠臓顟B(tài)是大于0的,這個(gè)時(shí)候需要一直往當(dāng)前節(jié)點(diǎn)的前置節(jié)點(diǎn)找,一直找到不是取消狀態(tài)的節(jié)點(diǎn)。這里為什么不給waitStatus設(shè)置默認(rèn)為SIGNAL呢,減少循環(huán)次數(shù)。博客園有個(gè)大神說(shuō)過(guò),這是jdk大神故意為之,這樣做盡量多執(zhí)行一次獲取鎖的動(dòng)作,類似自旋。因?yàn)槿绻@里判斷為true后,該線程多半會(huì)阻塞 ,阻塞是會(huì)引起線程上下文切換降低吞吐量,很有道理。接著看:

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

LockSupport.park是jdk提供的unsafe方法,大意就是阻塞該線程。并返回線程的中斷狀態(tài)。當(dāng)然前置節(jié)點(diǎn)的線程沒(méi)有unlock之前是不會(huì)返回的啦。lock方法的閱讀就到此為止了。下面接著看unlock代碼。圖就不畫了,直接開始。

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

這邊意思比較簡(jiǎn)單,如果當(dāng)前線程釋放鎖成功,則喚醒隊(duì)列頭節(jié)點(diǎn)的后置節(jié)點(diǎn),先看下tryRelease。

protected final boolean tryRelease(int releases) {
          int c = getState() - releases;
          if (Thread.currentThread() != getExclusiveOwnerThread())
              throw new IllegalMonitorStateException();
          boolean free = false;
          if (c == 0) {
              free = true;
              setExclusiveOwnerThread(null);
          }
          setState(c);
          return free;
      }

releases值是1,狀態(tài)值減1為0的話說(shuō)明鎖已經(jīng)被 完全釋放,持有鎖的線程設(shè)置為null。如果不為0說(shuō)明鎖被重入過(guò),這也說(shuō)明了。調(diào)用了多少次lock,就必須有多少次unlock對(duì)應(yīng),不然就沒(méi)有完全釋放鎖。然后看下unparkSuccessor。

 private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        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);
    } 
    

先把頭節(jié)點(diǎn)waitStatus設(shè)置為默認(rèn)值。獲取頭結(jié)點(diǎn)的后置節(jié)點(diǎn),如果后置節(jié)點(diǎn)為null或者狀態(tài)為取消,然后從鏈表的尾部開始遍歷,直到找到鏈表中第一個(gè)不是取消狀態(tài)的節(jié)點(diǎn)。這里為什么不直接從頭結(jié)點(diǎn)往下遍歷呢?最后再取消阻塞該節(jié)點(diǎn)。這個(gè)節(jié)點(diǎn)又開始執(zhí)行acquireQueued的邏輯啦。

最后說(shuō)一下公平鎖和非公平鎖中的區(qū)別??聪鹿芥i的邏輯。

 protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }

相對(duì)非公平鎖多了hasQueuedPredecessors的判斷,這個(gè)方法的大概意思就是隊(duì)列中是否存在等待的節(jié)點(diǎn)。如果有的話,公平鎖的邏輯就不能直接嘗試持有鎖,而是需要進(jìn)入到隊(duì)列中排隊(duì)。

ReentrantLock的關(guān)鍵方法已經(jīng)分析完了,雖然是鎖,但是jdk大神的設(shè)計(jì)中無(wú)處不提現(xiàn)出樂(lè)觀的思想,減少因?yàn)榫€程阻塞引起的上下文切換,這也是為什么之前說(shuō)ReentrantLock比synchronized的性能要好的多,當(dāng)然synchronized經(jīng)過(guò)各種優(yōu)化已經(jīng)很好很好啦,同樣是用了樂(lè)觀鎖來(lái)替代悲觀的操作。

最后編輯于
?著作權(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)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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

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