一張圖讀懂Java非公平鎖與公平鎖

前言

文本已收錄至我的GitHub倉庫,歡迎Star:https://github.com/bin392328206/six-finger
「種一棵樹最好的時間是十年前,其次是現在」
我知道很多人不玩「qq」了,但是懷舊一下,歡迎加入六脈神劍Java菜鳥學習羣,羣聊號碼:「549684836」 鼓勵大家在技術的路上寫博客

絮叨

今天再看多線程的知識的時候,有個知識盲點,就是以前我自己理解的公平鎖和非公平鎖,就是非公平以爲就是所有的雙向隊列中的元素都有資格去競爭資源的權力,但是看了下源碼,然後查了寫資料,發現自己以前的理解完全是錯的,不知道有多少小夥伴和小六六一樣的呢?今日,小六六借用人家的文章給大家好好捋捋這2個概念

相關概念

在Java併發編程中,公平鎖與非公平鎖是很常見的概念,ReentrantLock、ReadWriteLock默認都是非公平模式,非公平鎖的效率爲何高於公平鎖呢?究竟公平與非公平有何區別呢?

首先先簡單從名字上來理解,公平鎖就是保障了多線程下各線程獲取鎖的順序,先到的線程優先獲取鎖,而非公平鎖則無法提供這個保障。看到網上很多說法說非公平鎖獲取鎖時各線程的的概率是隨機的,這也是一種很不確切的說法。非公平鎖並非真正隨機,其獲取鎖還是有一定順序的,但其順序究竟是怎樣呢?先看畫了半天的圖:

圖解公平&非公平

公平鎖與非公平鎖的一個重要區別就在於上圖中的2、6、10那個步驟,對應源碼如下:

  //非公平鎖
 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;
        }


  //公平鎖
  protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
              //hasQueuedPredecessors這個方法就是最大區別所在
                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;
        }

分析以上代碼,我們可以看到公平鎖就是在獲取鎖之前會先判斷等待隊列是否爲空或者自己是否位於隊列頭部,該條件通過才能繼續獲取鎖。在結合兔子喝水的圖分析,非公平鎖獲取所得順序基本決定在9、10、11這三個事件發生的先後順序, 1、若在釋放鎖的時候總是沒有新的兔子來打擾,則非公平鎖等於公平鎖;2、若釋放鎖的時候,正好一個兔子來喝水,而此時位於隊列頭的兔子還沒有被喚醒(因爲線程上下文切換是需要不少開銷的),此時後來的兔子則優先獲得鎖,成功打破公平,成爲非公平鎖;

其實對於非公平鎖,只要線程進入了等待隊列,隊列裏面依然是FIFO的原則,跟公平鎖的順序是一樣的。因爲公平鎖與非公平鎖的release()部分代碼是共用AQS的代碼。

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

上文說到的線程切換的開銷,其實就是非公平鎖效率高於公平鎖的原因,因爲非公平鎖減少了線程掛起的機率,後來的線程有一定機率逃離被掛起的開銷。

結尾

相信大家對公平和非公平鎖的概念應該有了比較清晰的認識了

日常求贊

好了各位,以上就是這篇文章的全部內容了,能看到這裏的人呀,都是「真粉」

創作不易,各位的支持和認可,就是我創作的最大動力,我們下篇文章見

六脈神劍 | 文 【原創】如果本篇博客有任何錯誤,請批評指教,不勝感激 !**

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章