天天看點

Java并發 ReentrantLock(重入鎖)之公平鎖源碼解析 超詳細!!!鎖操作釋放鎖

上一篇講的是ReentrantLock的非公平鎖,這篇部落格就講講RenntrantLock的公平鎖。

公平鎖,就是線程老老實實的排隊,不會嘗試争搶資源。

還是舉上廁所的栗子(通俗易懂,牛的一批):你在公共廁所排隊上廁所,突然又有一個人進來,這回這個人講究的很,沒有去挨個開廁所門,而是直接排在你後面,等輪到他了他再上廁所。

其實公平鎖與非公平鎖的操作大部分都差不多,隻是在嘗試擷取鎖資源的地方不一緻,其他的排隊與喚醒等操作都是一緻的。是以本篇部落格就隻講公平鎖與非公平鎖不一緻的地方供大家參考。一緻的部分我就不詳細講了,如有不懂的看上一篇部落格就好。

鎖操作

先上我的測試代碼

public static void main(String[] args) {
	ReentrantLock reentrantLock = new ReentrantLock(true);
	reentrantLock.lock();
	reentrantLock.unlock();
}
           

這回,在建立ReentrantLock對象的時候我帶了一個布爾參數true。表示我要建立的鎖是公平鎖。

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

在公平鎖嘗試擷取鎖的時候有什麼不一緻的呢?接下來詳細的分析一下

final void lock() {
	acquire(1);
}
           

如果大家看了上一篇非公平鎖的部落格,就可以一眼看出不一緻的地方。在非公平鎖裡面,上鎖首先就嘗試進行鎖狀态數的更新。如果更新失敗了,才會去嘗試擷取鎖,而在公平鎖裡面,則直接就進行嘗試擷取鎖,不管目前鎖是不是空閑的。

public final void acquire(int arg) {
	if (!tryAcquire(arg) &&
		acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
	selfInterrupt();
}
           

在嘗試擷取鎖這裡公平鎖與非公平鎖都是一緻的。接下來看tryAcquire(arg)方法,這個方法與非公平鎖的tryAcquire方法有一些不一緻

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

第一步,擷取目前線程,其次擷取目前鎖的狀态數。

其次開始判斷目前狀态數是否為0,如果為0則進一步判斷目前是否有程序在排隊如果沒有則嘗試進行狀态數的更新。如果更新成功了則将獨占鎖的線程設定成目前線程。如果目前狀态數不為0,則接着判斷目前線程與獨占鎖線程是不是同一個,如果是同一個線程,則可以進入到鎖裡面,将狀态數加一,判斷狀态數有沒有超過重入最大次數,超過了則抛異常,沒有就将狀态數更新,然後傳回true。

都不符合,則傳回false。

注:标紅的地方就是公平鎖與非公平鎖不一緻的地方。

接下來看一下hasQueuedPredecessors()具體操作

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

操作還是比較簡單的 先擷取到目前連結清單的頭與尾節點,如果頭結點不等于尾節點并且h.next為null或者頭節點的後繼節點存儲的線程不是目前線程。則判斷目前有線程在排隊。

注:h != t && h.next == null 為什麼成立就會判斷目前有線程在排隊呢?原因就是h != t 表示頭結點與尾節點之間還有其他節點。在非公平鎖裡面有一個enq方法,在添加節點的時候是先設定前置節點,然後在設定後繼節點。可能會出現執行到擷取h.next的時候剛好另一個線程設定完前置節點還沒有設定後繼節點。是以會出現後繼節點為null的情況。

當線程嘗試擷取鎖失敗的時候,後續的操作就與非公平鎖一緻了,先在連結清單中添加節點,然後再進行嘗試排隊。排隊成功後,将這個線程挂起。

至此公平鎖的鎖源碼就剖析完成了

釋放鎖

關于釋放鎖的操作,公平鎖與非公平鎖一緻。是以看非公平鎖的解析就可以。

PS:如有不正确的地方,歡迎同學指出。共同進步,謝謝