ReentrantLock之公平與非公平模式

ReentrantLock之公平與非公平模式

ReentrantLock是java重要的鎖之一,這裏只討論其公平和非公平模式。

1. 初始化

ReentrantLock有兩個構造函數,通過構造函數我們可以看到
1. 默認使用的是非公平模式。
2. 通過有參的構造函數可以指定使用公平還是非公平模式。

    public ReentrantLock() {
        sync = new NonfairSync();
    }

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

2. 公平與非公平實現

ReentrantLock是通過一個靜態內部內Sync實現的,Sync繼承自AbstractQueuedSynchronizer,並實現了其抽象方法。AbstractQueuedSynchronizer是java的一個抽象的同步器框架,大多數的java同步工具類都是由其實現。而NonfairSync和FairSync通過繼承Sync實現鎖的功能。接下來看下是如何實現公平與非公平的。

2.1 NonfairSync的非公平性實現

NonfairSync的實現代碼如下:

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
         //加鎖方法
        final void lock() {
        	//cas,如果當前state=0則將state替換爲1
            if (compareAndSetState(0, 1))
            	//替換成功,則表示獲得鎖,同時將當exclusiveOwnerThread設置爲當前線程,之後會通過該值是否是當前線程來確定是否重入
                setExclusiveOwnerThread(Thread.currentThread());
            else
            	//替換失敗,表示獲取鎖失敗,此時通過acquire(1)進一步獲取鎖
                acquire(1);
        }

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

如上圖代碼所示,當使用非公平模式時,ReentrantLock.lock()實際上是通過調用NonfairSync.lock()獲取鎖。
NonfairSync 的lock()方法的實現如上面的註釋所示:

  1. 通過CAS 替換AbstractQueuedSynchronizer的state,如果當前state的值爲0則將其替換爲1,這種情況只會發生在當前沒有線程獲取到鎖的時候。
  2. 如果替換成功了,表示獲得了鎖,由cas保證併發情況下的正確性。同時將exclusiveOwnerThread設置爲當前線程。
  3. 如果替換失敗了,表明state已經不是0了,已經有線程獲取到鎖了,但是此時無法確定是否是當前線程獲取到鎖的,所以在這裏還需要調用一次acquire(1),進一步判斷是否能夠獲得鎖。 acquire(int)是AbstractQueuedSynchronizer的方法,該方法會通過 tryAcquire(int acquires) 進行判斷是否能夠獲取到鎖,如果**tryAcquire(int acquires)**返回true表示獲取成功,否則將當前線程加入同步隊列,進行排隊獲取鎖。

接着看nonfairTryAcquire(acquires);是怎麼實現的:

final boolean nonfairTryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            //state==0表示沒有線程獲取到鎖
            if (c == 0) {
            	//這段代碼同上面一樣
                if (compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //獲取鎖失敗,判斷當前線程是否與擁有鎖的線程即exclusiveOwnerThread的值相等,如果相等表示是當前線程獲取到鎖的,則
            //可以進入(重入)鎖,並將state的值+1
            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;
        }
  1. 在上面的代碼中可以看到,nonfairTryAcquire在進入的時候又進行了一次state是否爲0的判斷,如果鎖的獲取與釋放足夠快的話那麼這段代碼的價值就會非常的高
  2. 在經過步驟4的再次判斷已經有線程獲取到鎖後,繼續判斷當前獲取到鎖的線程是否是當前線程,如果是當前線程則將state的值加上acquires,由於nonfairTryAcquire傳入的是1,所以如果是當前線程則將state的值加1,通過累加state來實現可以重入的功能,並且在釋放後需將state-1,只有當state=0時才表示釋放鎖。即同一個線程加鎖多少次也需要釋放多少次注意在這裏對state的累加及設值都沒有使用同步,因爲只會是獲取到鎖的線程纔會執行到該段代碼,並不會出現併發問題
  3. 如果state!=0(有線程獲取到鎖),且獲取到鎖的線程也不是當前線程時,獲取鎖失敗。

由上面的實現可知:非公平情況下,每個線程獲取鎖的時候,先進行cas的判斷是否能夠獲取到鎖,如果是多個線程併發執行的話每個線程都有機會獲取到鎖。

2.2 FairSync 的公平性實現

FairSync 的代碼如下

static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

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

如上所示

  1. FairSync 並沒有像NonfairSync在lock方法中進行cas替換state獲取鎖,而是通過acquire(1)調用tryAcquire(int acquires) 方法獲取鎖。
  2. FairSync 的 tryAcquire(int acquires) 與NonfairSync的tryAcquire(int acquires)十分相似,只是FairSync 多了 !hasQueuedPredecessors() 的判斷 ,而hasQueuedPredecessors()就體現了公共性

hasQueuedPredecessors()是AbstractQueuedSynchronizer的方法:

 public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // 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());
    }

代碼如上所示,其方法代表的意思是在同步隊列中是否有比當前線程等待更久的線程。如果返回true,表示有線程在同步隊列中且先於自身等待鎖,則當前線程不能繼續獲取鎖。

由於state=0表示當前沒有線程獲取到鎖,所以接下來就需要對鎖進行爭奪。而hasQueuedPredecessors() 對爭奪鎖的資格進行了一次判斷。

  • 首先判斷頭節點和尾節點是否相等,如果頭節點等於尾節表明同步隊列中沒有任何節點,即沒有任何線程獲取到鎖,此時可以進行鎖的獲取,返回true。
  • 如果頭節點與尾節點不相等,並且原頭節點的後繼節點爲null ,表明已有其它線程先於自己得到獲取鎖的資格。這種情況只會發生在不同線程同時獲取鎖的情況下,已經有其中一個線程通過acquireQueued(final Node node, int arg)方法獲取到鎖,這時會將當前節點設置爲head,並將原head的next設置爲null。但是其它線程將head賦值給h的操作先於成功獲取鎖的線程的setHead(node)操作,然後這個線程執行完 p.next = null,後其餘線程可能會發現(s = h.next) == null 。
 final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    //將原head的next設置爲null,
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
x t1 t2
s1 Node h = head;
s2 setHead(node);
p.next = null;
s3 (s = h.next) == null

這時t2已經獲取到鎖

  • 如果頭節點與尾節點不相等,並且頭節點的後繼節點不爲null,這中情況下表明已經有線程進入同步隊列了,此時肯定有線程進行過鎖的獲取,但是還需要判斷這個線程是否是自身,如果不是自身則表示已經有其它線程先於自身請求鎖,此時爭奪鎖失敗乖乖進入同步隊列排隊獲取鎖。
x t1 t2
s1 setHead(node);
p.next = null;
s2 Node h = head;
s3 (s = h.next) == null || s.thread != Thread.currentThread()

這時t2已經獲取到鎖

如果真的是十分的巧合,恰好有多個線程通過了hasQueuedPredecessors() 返回false,表明自己都是最先獲取鎖,這種情況值發生在同時獲取鎖,且還沒有任何一個線程被添加到同步隊列當中(這時還沒有其它線程執行完addWaiter方法,只要有任何線程執行完addWaiter,hasQueuedPredecessors都會返回true)。此時還需要進行一次compareAndSetState(0, acquires) CAS操作避免多個線程獲取到鎖。

3. 總結

通過上面的分析可以發現:

  1. 非公平鎖在獲取鎖的時候是先直接進行CAS替換state的值,如果替換成功則獲取到鎖,並不進行排序操作
  2. 公平鎖在獲取鎖的時候通過hasQueuedPredecessors() 方法判斷是否有其它線程等待獲取鎖的時間超過當前線程,如果有則表明同步隊列中已有線程先行進行等待獲取鎖。
  3. 非公平鎖的非公平只體現在第一次獲取鎖的時候,如果第一次獲取不到鎖,那麼則進入同步隊列,同步隊列中的線程獲取鎖的順序則是先進先出的。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章