背景知識
ReentrantLock的組成
首先看下ReentrantLock的組成結構
公平鎖和非公平鎖主要是通過內部類FairSync和NonFairSync中的tryAquire()方法來區(qū)分的
概述
ReentrantLock中的公平鎖和非公平鎖的主要區(qū)別為
- 公平鎖中, 線程嚴格按照先進先出(FIFO)的順序, 獲取鎖資源
- 非公平鎖中, 擁有鎖的線程在釋放鎖資源的時候, 當前嘗試獲取鎖資源的線程可以和等待隊列中的第一個線程競爭鎖資源, 這就是ReentrantLcok中非公平鎖的含義; 但是已經進入等待隊列的線程, 依然是按照先進先出的順序獲取鎖資源
公平鎖示意圖
非公平鎖示意圖
注意: 以上圖片中的Head是一個特殊的節(jié)點, 僅用于構造等待隊列這個數據結構, 不包含等待的線程信息, 所以下一個可以獲取鎖資源的節(jié)點是Node1節(jié)點
源碼解讀
非公平鎖
static final class NonfairSync extends Sync {
/**
* Performs lock. Try immediate barge, backing up to normal
* acquire on failure.
*/
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
Sync中的nonfairTryAcquire()方法
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// flag 1 公平鎖和非公平鎖的主要不同點
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()方法是Sync類中實現(xiàn)的,注意flag 1處的代碼, 只要state=0, 當前線程就有機會搶占該鎖資源, 不考慮等待隊列中是否已經有等待的線程
公平鎖
static final class FairSync extends Sync {
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) {
// flag 2 公平鎖和非公平鎖的主要區(qū)別
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;
}
}
flag 2處的代碼顯示, 競爭鎖資源的前提條件是!hasQueuedpredecessord(), 即當前隊列中沒有其他線程在等待鎖資源, 代碼如下:
public final boolean hasQueuedPredecessors() {
//he correctness of this depends on head being initialized
//before tail and on head.next being accurate if thecurrent
//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==t意味著隊列為空,返回false
- (h!=t) && (s=h.next)==null 意味著有一個線程嘗試獲取鎖失敗后,正在進行入隊操作,而且 在AQS中國年enq()方法中, head=tail方法正好還沒執(zhí)行到, 此時隊列被認為不空, 返回true, enq()代碼如下:
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
// Head節(jié)點已經創(chuàng)建, 初始化操作完成了一半, tail=head尚未來執(zhí)行,
// 到此已經能確定隊列中存在等待鎖資源的線程
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
- (h==t) && s.thread != Thread.currentThread() 此時隊列不空, 但是第一個節(jié)點, 即上圖中的Node1和當前線程是同一個線程, 意味著在當前線程之前沒有其他等待的線程, 此時返回false
總之, hasQueuedPredecessors()方法只有返回false,當前線程才有資格嘗試獲取鎖資源;假如返回true, 則意味著他必須排隊等待
代碼對比
公平鎖
if (c == 0) {
// flag 2 公平鎖和非公平鎖的主要區(qū)別
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
非公平鎖
if (c == 0) {
// flag 1 公平鎖和非公平鎖的主要區(qū)別
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
問題
閱讀ReenTrantLock.FairSync.tryAcquire()方法,遇到兩個有意思的問題
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
// flag 3 設置state使用了CAS
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
// flag 4 競爭的線程總數查過Int的最大值, 直接報錯
throw new Error("Maximum lock count exceeded");
// flag 5 同樣是設置state值, 此處卻沒有使用CAS
setState(nextc);
return true;
}
return false;
}
同樣是state狀態(tài),flag 3 處使用了CAS, flag 5 處卻沒有使用CAS, 這是為啥呢? 因為當前線程為已持有鎖的線程時(current == getExclusiveOwnerThread()), 只有他自己能修改state狀態(tài), 所以無需考慮并發(fā)修改的情況
flag 4 處競爭的線程總數超過int的最大值時, 會直接報錯(拋出"Maximum lock count exceeded"異常), 然后在finally代碼塊中執(zhí)行cancelAquire()操作, 將該線程對應的節(jié)點移出隊列, 最后將異常拋給調用方, 調用方線程停止運行, 如下所示:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//flag 6
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
知識擴展
tryLock方法
這里同時補充一點, 不管Reentrant使用公平鎖(FairLock)還是非公平鎖(NonFairLock), ReentrantLock類的tryLock()方法都會調用Sync類的nonfairTryAcquire()方法,采取非公平的競爭策略
參考資料
公平鎖與非公平鎖
https://blog.csdn.net/m47838704/article/details/80013056
一些比較難看懂的方法
https://www.cnblogs.com/kumu/p/10659835.html