ReentrantLock中公平鎖和非公平鎖的區別

目錄

背景知識

ReentrantLock的組成

概述

公平鎖示意圖

非公平鎖示意圖

源碼解讀

非公平鎖

公平鎖

代碼對比

問題

知識擴展

tryLock方法

參考資料


背景知識

ReentrantLock的組成

首先看下ReentrantLock的組成結構。

公平鎖和非公平鎖主要是通過內部類FairSync和NonFairSync中的tryAquire()方法來區分的。

概述

ReentrantLock中的公平鎖和非公平鎖的主要區別爲

  1. 公平鎖中, 線程嚴格按照先進先出(FIFO)的順序, 獲取鎖資源;
  2. 非公平鎖中, 擁有鎖的線程在釋放鎖資源的時候, 當前嘗試獲取鎖資源的線程可以和等待隊列中的第一個線程競爭鎖資源, 這就是ReentrantLcok中非公平鎖的含義; 但是已經進入等待隊列的線程, 依然是按照先進先出的順序獲取鎖資源;

公平鎖示意圖

非公平鎖示意圖

注意: 以上圖片中的Head是一個特殊的節點, 僅用於構造等待隊列這個數據結構, 不包含等待的線程信息, 所以下一個可以獲取鎖資源的節點是Node1節點。

源碼解讀

非公平鎖

    static final class NonfairSync extends Sync {
        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

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

Sync中的nonfairTryAcquire()方法。

        final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // flag 1 公平鎖和非公平鎖的主要不同點 
                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;
        }

其中nonfairTryAcquire()方法是Sync類中實現的,注意flag 1處的代碼, 只要state=0, 當前線程就有機會搶佔該鎖資源, 不考慮等待隊列中是否已經有等待的線程。

公平鎖

static final class FairSync extends Sync {
        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                // flag 2 公平鎖和非公平鎖的主要區別
                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;
        }
    }

flag 2處的代碼顯示, 競爭鎖資源的前提條件是!hasQueuedpredecessord(), 即當前隊列中沒有其他線程在等待鎖資源, 代碼如下:

    public final boolean hasQueuedPredecessors() {
        //he correctness of this depends on head being initialized
        //before tail and on head.next being accurate if thecurrent
        //thread is first in queue.
        Node t = tail; //Read fields in reverse initialization order
        Node h = head;
        Node s;
        return h != t &&
            (
             (s = h.next) == null ||
             s.thread != Thread.currentThread()
            );
    }

簡單解釋一下這段代碼:

  • h==t意味着隊列爲空,返回false
  • (h!=t) && (s=h.next)==null 意味着有一個線程嘗試獲取鎖失敗後,正在進行入隊操作,而且 在AQS的enq()方法中, head=tail方法正好還沒執行到, 此時隊列被認爲不空, 返回true, enq()代碼如下:
      private Node enq(final Node node) {
          for (;;) {
              Node t = tail;
              if (t == null) { // Must initialize
                  if (compareAndSetHead(new Node()))
                  
                  // Head節點已經創建, 初始化操作完成了一半, tail=head尚未來執行,
                  // 到此已經能確定隊列中存在等待鎖資源的線程
                      tail = head;
              } else {
                  node.prev = t;
                  if (compareAndSetTail(t, node)) {
                      t.next = node;
                      return t;
                  }
              }
          }
      }
  • (h==t) && s.thread != Thread.currentThread() 此時隊列不空, 但是第一個節點, 即上圖中的Node1和當前線程是同一個線程, 意味着在當前線程之前沒有其他等待的線程, 此時返回false

總之, hasQueuedPredecessors()方法只有返回false,當前線程纔有資格嘗試獲取鎖資源:假如返回true, 則意味着他必須排隊等待。

代碼對比

公平鎖

            if (c == 0) {
                // flag 2 公平鎖和非公平鎖的主要區別
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }

非公平鎖

            if (c == 0) {
                // flag 1 公平鎖和非公平鎖的主要區別
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }

問題

閱讀ReenTrantLock.FairSync.tryAcquire()方法,遇到兩個有意思的問題:

protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    // flag 3 設置state使用了CAS
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    // flag 4 競爭的線程總數查過Int的最大值, 直接報錯
                    throw new Error("Maximum lock count exceeded");
                // flag 5 同樣是設置state值, 此處卻沒有使用CAS
                setState(nextc);
                return true;
            }
            return false;
        }
  1. 同樣是state狀態,flag 3 處使用了CAS, flag 5 處卻沒有使用CAS, 這是爲啥呢? 因爲當前線程爲已持有鎖的線程時(current == getExclusiveOwnerThread()), 只有他自己能修改state狀態, 所以無需考慮併發修改的情況;

  2. flag 4 處競爭的線程總數超過int的最大值時, 會直接報錯(拋出"Maximum lock count exceeded"異常), 然後在finally代碼塊中執行cancelAquire()操作, 將該線程對應的節點移出隊列, 最後將異常拋給調用方, 調用方線程停止運行, 如下所示:

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                //flag 6 
                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);
        }
    }

知識擴展

tryLock方法

這裏同時補充一點, 不管Reentrant使用公平鎖(FairLock)還是非公平鎖(NonFairLock), ReentrantLock類的tryLock()方法都會調用Sync類的nonfairTryAcquire()方法,採取非公平的競爭策略。

參考資料

《公平鎖與非公平鎖》

《一些比較難看懂的方法》

發佈了247 篇原創文章 · 獲贊 512 · 訪問量 1255萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章