Java併發--重入鎖ReentrantLock公平性與非公平性的區別

前言

重入鎖ReentrantLock有兩種方式獲取鎖,一種是公平性,一種是非公平性。如果在絕對時間上,先請求獲取鎖的線程一定會先獲取到鎖,那麼這個鎖就是公平的,反之,這個鎖就是不公平的。公平的獲取鎖,也就是等待時間最長的線程最優先獲取鎖,也可以說鎖獲取是順序的。那重入鎖ReentrantLock如何實現公平鎖,以及非公平鎖,它們的區別又是什麼?

正文

重入鎖ReentrantLock默認使用非公平獲取鎖,同時提供構造函數,能夠控制鎖是否是公平。

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

公平鎖使用FairSync同步組件,非公平鎖使用NonfairSync同步組件。FairSync與NonfairSync均是ReentrantLock的內部類,又共同繼承了ReentrantLock的一個內部類Sync。FairSync與NonfairSync不同的地方在於重寫Sync的lock()方法與tryAcquire()方法。我們先看一下重入鎖ReentrantLock默認的非公平鎖如何獲取鎖。

非公平獲取鎖

非公平性獲取鎖調用NonfairSync的lock()方法

        final void lock() {
            // 如果當前同步狀態爲O,表示鎖未被任何線程獲取,CAS設置同步狀態爲1
            if (compareAndSetState(0, 1))
                // 設置當前線程爲獲取鎖的線程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                // 如果鎖已經被某線程獲取,嘗試獲取鎖
                acquire(1);
        }

NonfairSync的lock()方法會調用同步器acquire方法,最終會調用NonfairSync的nonfairTryAcquire方法來獲取同步狀態。

        final boolean nonfairTryAcquire(int acquires) {
            // 獲取當前線程
            final Thread current = Thread.currentThread();
            // 獲取同步狀態
            int c = getState();
            // 同步狀態爲0,表示沒有線程獲取鎖
            if (c == 0) {
                // CAS獲取同步狀態
                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會判斷同步狀態是否爲0,如果同步狀態是0,說明目前沒有任何線程獲取到鎖,設置獲取鎖的線程爲當前線程並返回true,表示同步狀態成功。當同步狀態不爲0時,判斷當前線程是否爲獲取鎖的線程,如果是獲取鎖的線程再次請求,則將同步狀態的值進行增加並返回true,表示同步狀態成功。

在非公平性獲取鎖中,只要CAS設置同步狀態成功,則表示當前線程獲取了鎖。那公平性是如何獲取鎖?

公平性獲取鎖

公平性獲取鎖調用FairSync的lock()方法

        final void lock() {
            // 獲取鎖
            acquire(1);
        }

FairSync的lock()方法調用同步器acquire方法,最終會調用FairSync的tryAcquire方法來獲取同步狀態。

        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            // 同步狀態爲0,表示沒有線程獲取鎖
            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;
        }
    }

我們把該方法與非公平獲取鎖時調用的nonfairTryAcquire相比較,唯一不同的位置在於當同步狀態爲0時,增加hasQueuedPredecessors判斷,而該方法判斷在同步隊列中當前節點是否有前驅節點,如果該方法返回true,表示有線程比當前線程更早的請求獲取鎖,因此需要等待前驅線程獲取並釋放鎖後才能繼續獲取鎖。

注:hasQueuedPredecessors方法爲同步器AbstractQueuedSynchronizer中的方法,鎖的語義均由同步器AbstractQueuedSynchronizer實現。

    public final boolean hasQueuedPredecessors() {
        // 同步隊列尾節點
        Node t = tail; 
        // 同步隊列頭節點
        Node h = head;
        Node s;
        // 判斷頭節點的後繼節點的線程是否爲當前線程,即判斷當前節點是否有前驅節點
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }

重入鎖ReentrantLock在獲取鎖時通過調用hasQueuedPredecessors方法來實現公平鎖的語義。

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