重入鎖ReentrantLock,顧名思義,就是支持重進入的鎖,它表示該鎖能夠支持一個線程對資源的重復加鎖。除此之外,該鎖的還支持獲取鎖時的公平和非公平性選擇。
ReentrantLock雖然沒能像synchronized關鍵字一樣支持隱式的重進入,但是在調用lock()方法時,已經獲取到鎖的線程,能夠再次調用lock()方法獲取鎖而不被阻塞。
ReentrantLock提供了一個構造函數,能夠控制鎖是否是公平的(如果在絕對時間上,先對鎖進行獲取的請求一定先被滿足,那么這個鎖是公平的,反之,是不公平的)。事實上,公平的鎖機制往往沒有非公平的效率高,但是,并不是任何場景都是以TPS作為唯一的指標,公平鎖能夠減少“饑餓”發生的概率,等待越久的請求越是能夠得到優先滿足。
1 實現重入
- 線程再次獲取鎖
鎖需要去識別獲取鎖的線程是否為當前占據鎖的線程,如果是,則再次成功獲取。 - 鎖的最終釋放
線程重復n次獲取了鎖,隨后在第n次釋放該鎖后,其他線程能夠獲取到該鎖。鎖的最終釋放要求鎖對于獲取進行計數自增,計數表示當前鎖被重復獲取的次數,而鎖被釋放時,計數自減,當計數等于0時表示鎖已經成功釋放。
ReentrantLock的nonfairTryAcquire方法:
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 如果同步狀態為0,即沒有線程獲取鎖
if (c == 0) {
// 嘗試獲取鎖
if (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;
}
ReentrantLock的tryRelease方法:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 當同步狀態為0時,將占有線程設置為null,并返回true,表示釋放成功
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
2 公平與非公平獲取鎖的區別
上一小節中介紹的nonfairTryAcquire(int acquires)方法,對于非公平鎖,只要CAS設置同步狀態成功,則表示當前線程獲取了鎖,而公平鎖則不同,代碼如下所示:
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 唯一不同的位置為判斷條件多了hasQueuedPredecessors()方法
// 即加入了同步隊列中當前節點是否有前驅節點的判斷,如果該
// 方法返回true,則表示有線程比當前線程更早地請求獲取鎖
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;
}
3 性能對比
公平性鎖保證了鎖的獲取按照FIFO原則,而代價是進行大量的線程切換。
非公平性鎖雖然可能造成線程“饑餓”,但極少的線程切換,保證了其更大的吞吐量。