ReentrantLock中公平鎖和非公平鎖的區(qū)別

背景知識

ReentrantLock的組成

首先看下ReentrantLock的組成結構

ReentrantLock類圖

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

概述

ReentrantLock中的公平鎖和非公平鎖的主要區(qū)別為

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

公平鎖示意圖

公平鎖示意圖

非公平鎖示意圖

非公平鎖示意圖

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

源碼解讀

非公平鎖

    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類中實現(xiàn)的,注意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 公平鎖和非公平鎖的主要區(qū)別
                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方法正好還沒執(zhí)行到, 此時隊列被認為不空, 返回true, enq()代碼如下:
      private Node enq(final Node node) {
          for (;;) {
              Node t = tail;
              if (t == null) { // Must initialize
                  if (compareAndSetHead(new Node()))
                  
                  // Head節(jié)點已經創(chuàng)建, 初始化操作完成了一半, tail=head尚未來執(zhí)行,
                  // 到此已經能確定隊列中存在等待鎖資源的線程
                      tail = head;
              } else {
                  node.prev = t;
                  if (compareAndSetTail(t, node)) {
                      t.next = node;
                      return t;
                  }
              }
          }
      }
  • (h==t) && s.thread != Thread.currentThread() 此時隊列不空, 但是第一個節(jié)點, 即上圖中的Node1和當前線程是同一個線程, 意味著在當前線程之前沒有其他等待的線程, 此時返回false

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

代碼對比

公平鎖

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

非公平鎖

            if (c == 0) {
                // flag 1 公平鎖和非公平鎖的主要區(qū)別
                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狀態(tài),flag 3 處使用了CAS, flag 5 處卻沒有使用CAS, 這是為啥呢? 因為當前線程為已持有鎖的線程時(current == getExclusiveOwnerThread()), 只有他自己能修改state狀態(tài), 所以無需考慮并發(fā)修改的情況

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

    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()方法,采取非公平的競爭策略

參考資料

公平鎖與非公平鎖
https://blog.csdn.net/m47838704/article/details/80013056
一些比較難看懂的方法
https://www.cnblogs.com/kumu/p/10659835.html

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末罪既,一起剝皮案震驚了整個濱河市铸题,隨后出現(xiàn)的幾起案子铡恕,更是在濱河造成了極大的恐慌,老刑警劉巖丢间,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件探熔,死亡現(xiàn)場離奇詭異,居然都是意外死亡烘挫,警方通過查閱死者的電腦和手機诀艰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來饮六,“玉大人其垄,你說我怎么就攤上這事÷遍希” “怎么了绿满?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長窟扑。 經常有香客問我喇颁,道長,這世上最難降的妖魔是什么嚎货? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任橘霎,我火速辦了婚禮,結果婚禮上殖属,老公的妹妹穿的比我還像新娘姐叁。我一直安慰自己,他們只是感情好忱辅,可當我...
    茶點故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布七蜘。 她就那樣靜靜地躺著,像睡著了一般墙懂。 火紅的嫁衣襯著肌膚如雪橡卤。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天损搬,我揣著相機與錄音碧库,去河邊找鬼。 笑死巧勤,一個胖子當著我的面吹牛嵌灰,可吹牛的內容都是我干的。 我是一名探鬼主播颅悉,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼沽瞭,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了剩瓶?” 一聲冷哼從身側響起驹溃,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤城丧,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后豌鹤,有當地人在樹林里發(fā)現(xiàn)了一具尸體亡哄,經...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年布疙,在試婚紗的時候發(fā)現(xiàn)自己被綠了蚊惯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡灵临,死狀恐怖截型,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情俱诸,我是刑警寧澤菠劝,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站睁搭,受9級特大地震影響赶诊,放射性物質發(fā)生泄漏。R本人自食惡果不足惜园骆,卻給世界環(huán)境...
    茶點故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一舔痪、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧锌唾,春花似錦锄码、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至余黎,卻和暖如春重窟,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背惧财。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工巡扇, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人垮衷。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓厅翔,卻偏偏與公主長得像,于是被迫代替她去往敵國和親搀突。 傳聞我的和親對象是個殘疾皇子刀闷,可洞房花燭夜當晚...
    茶點故事閱讀 45,033評論 2 355

推薦閱讀更多精彩內容