1.前言
Java 中的讀寫鎖實現是 ReentrantReadWriteLock ,是一種鎖分離策略。能有效提高讀比寫多的場景下的程式效能。
關於如何使用參見 併發程式設計之 Java 三把鎖。
由於讀寫鎖較為複雜,故分為篇文章進行原始碼分析,今天先說較為簡單的寫鎖。
2. 寫鎖介紹
不論是讀鎖還是寫鎖,都是基於 AQS 的,而 AQS 留給子類實現的就是 tryAcquire 或者 tryAcquireShared 方法,前者是寫鎖的實現,後者是讀鎖的實現,從名字上可以看出,一個是獨佔鎖,一個是共享鎖。
今天看的就是 tryAcquire 和 tryRelease 的實現,獲取和釋放。
3. tryAcquire 實現
原始碼加註釋如下:
// 如果有讀鎖,此時是獲取不到寫鎖的。當有寫鎖時,判斷重入次數。
// 當寫鎖空閒,讀鎖空閒,公平模式下,如果佇列中有等待的,不會搶鎖。非公平模式下,必搶鎖。
protected final boolean tryAcquire(int acquires) {
// 寫
Thread current = Thread.currentThread();
int c = getState();
// 用 state & 65535 得到低 16 位的值。
int w = exclusiveCount(c);
if (c != 0) {
// (Note: if c != 0 and w == 0 then shared count != 0)
// 如果 state 不是0,且低16位是0,說明了什麼?說明寫鎖是空閒的,讀鎖被霸佔了。那麼也不能拿鎖,返回 fasle。
// 如果低 16 位不是0,說明寫鎖被霸佔了,並且,如果持有鎖的不是當前執行緒,那麼這次拿鎖是失敗的。返回 fasle。
// 總之,當有讀鎖,就不能獲取寫鎖。當有寫鎖,就必須是重入鎖。
if (w == 0 || current != getExclusiveOwnerThread())
return false;
// 到這一步了,只會是寫重入鎖。如果寫重入次數超過最大值 65535,就會溢位。
if (w + exclusiveCount(acquires) > MAX_COUNT)
throw new Error("Maximum lock count exceeded");
// Reentrant acquire
// 將 state + 1
setState(c + acquires);
return true;
}
// 當 state 是 0 的時候,那麼就可以獲取鎖了。
// writerShouldBlock 判斷是否需要鎖。非公平情況下,返回 false。公平情況下,根據 hasQueuedPredecessors 結果判斷。
// 當佇列中有鎖等待了,就返回 false 了。
// 當是非公平鎖的時候,或者佇列中沒有等待節點的時候,嘗試用 CAS 修改 state。
if (writerShouldBlock() ||
!compareAndSetState(c, c + acquires))
return false;
// 修改成功 state 後,修改鎖的持有執行緒。
setExclusiveOwnerThread(current);
複製程式碼
詳解的邏輯都寫在註釋裡了,可以對照原始碼檢視,這裡再次總結這個方法的邏輯。
-
首先判斷鎖是否空閒。
-
如果空閒,則根據公平與否判斷是否應該獲取鎖,當
writerShouldBlock
返回結果是false
的時候,就使用 CAS 修改 state 變數,獲取鎖。成功之後修改 AQS 持有執行緒。 -
如果不是空閒的,則判斷寫鎖是否是空閒的,這裡有 2 種情況:
3.1 如果寫鎖空閒,但
state
不是0,說明有讀鎖,那麼就不能獲取鎖。3.2 如果寫鎖不空閒,判斷持有 AQS 鎖的執行緒是不是當前執行緒,如果不是,不能獲取,反之,可以獲取重入鎖。
4. tryRelease 實現
程式碼加註釋如下:
protected final boolean tryRelease(int releases) {
// 是否持有當前鎖
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
// 計算 state 值
int nextc = getState() - releases;
// 計算寫鎖的狀態,如果是0,說明是否成功。
boolean free = exclusiveCount(nextc) == 0;
// 釋放成功,設定持有鎖的執行緒為 null。
if (free)
setExclusiveOwnerThread(null);
// 設定 state
setState(nextc);
return free;
}
複製程式碼
這裡還是很簡單的,只是有一個地方需要注意:
// 計算寫鎖的狀態,如果是0,說明是否成功。
boolean free = exclusiveCount(nextc) == 0;
複製程式碼
這裡計算的只是 state 變數的低 16 的值,而不是整個 state 的值。雖然寫的時候,必然是序列的,但這裡計算的仍然是低 16 位的。
5. 總結
寫鎖在獲取鎖的時候,有幾個地方需要注意:當有讀鎖的時候,是不能獲取寫鎖的。寫鎖可以重入,但不能超過 65535 次。寫鎖狀態設定在 state 變數的低 16 位。
同時,在獲取鎖的時候,也會根據公平與否決定此次釋放需要獲取鎖。
如果是非公平的,直接嘗試CAS 修改 state ,獲取鎖。
如果是公平的,則根據 hasQueuedPredecessors 方法的返回值判斷,也就是如果佇列中有等待的執行緒,則依據公平策略,放棄此次獲取鎖的操作。反之,直接獲取鎖。