ReentrantLock-重入鎖
阿新 • • 發佈:2018-12-02
ReentrantLock
重入鎖, 表示該鎖支援一個執行緒對資源的重複加鎖
類結構
首先讓我們先看下 ReentrantLock 的類結構如下圖所示:
從圖中我們可以看出 ReentrantLock 實現 Lock 介面,同時內部類 Sync 是 AQS 的子類;而 Sync 又有兩個子類 NonfairSync 和 FairSync 分別對應非公平和公平鎖兩種策略。
構造
public ReentrantLock() {
sync = new NonfairSync();
}
複製程式碼
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
複製程式碼
ReentrantLock 預設採用非公平的策略,也可以在構造的時候指定是否公平的策略。
非公平鎖
非公平鎖是指在競爭獲取鎖的過程中,有可能後來者居上
lock() 獲取鎖
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 值為 1
if (compareAndSetState(0, 1))
// CAS 成功則說明獲取到鎖, 此時將當前執行緒設定為獨佔模式下鎖物件的持有者
setExclusiveOwnerThread(Thread.currentThread());
else
// CAS 失敗
// 可能是同一執行緒再次獲取鎖
// 也可能是不同執行緒獲取鎖
acquire(1);
}
protected final boolean tryAcquire(int acquires) {
// 呼叫父類 sync
return nonfairTryAcquire(acquires);
}
}
複製程式碼
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 此時說明已有執行緒釋放了鎖
// 有可能是同步佇列裡阻塞的執行緒被喚醒時嘗試獲取鎖
if (compareAndSetState(0, acquires)) {
// CAS 成功則說明獲取到鎖, 此時將當前執行緒設定為獨佔模式下鎖物件的持有者
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 說明同一執行緒再次獲取鎖
// state 加 1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
複製程式碼
獲取鎖的過程如下 :
- 通過 CAS 操作, 設定 state = 1
- 若 CAS 操作成功,則將當前執行緒設定為獨佔模式鎖物件的持有者
- 若 CAS 操作失敗, 最終會呼叫 sync 的方法 nonfairTryAcquire; 此時說明可能是同一執行緒再次嘗試獲取鎖,也有可能是其他執行緒嘗試獲取鎖
- 若當前 state == 0, 繼續執行前兩步操作
- 若當前 state != 0, 則判斷當前執行緒是否為鎖的持有者;若判斷成立,則對 state + 1
unlock() - 釋放鎖
非公平鎖的釋放呼叫的是父類 sync 的 tryRelease 方法
protected final boolean tryRelease(int releases) {
// state 減一操作
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
// 當前執行緒不是當前鎖的持有者時丟擲異常
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 只有 state == 0 時 才是真正完成鎖的釋放
free = true;
// 將鎖的持有者清空
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
複製程式碼
從釋放鎖的實現可以看出,獲取鎖與釋放鎖的操作是對等的,譬如下方虛擬碼:
ReentrantLock lock = new ReentrantLock();
public void do () {
lock.lock();
try {
do();
// 退出遞迴
} finally {
lock.unlock();
}
}
複製程式碼
公平鎖
公平鎖是指獲取鎖的順序完全符合請求時間的順序,也就是先到先得
lock() - 獲取鎖
接下來我們下公平鎖與非公平鎖在獲取鎖時有什麼不同
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 不同於非公平鎖操作,公平鎖多了個判斷條件 hasQueuedPredecessors
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;
}
複製程式碼
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;
// h != t 說明同步佇列已有等待的節點
// s = h.next == null 這個有點沒明白; head 的後置為空應該就是 head == tail 吧
// s.thread != Thread.currentThread 是判斷當前執行緒是不是同步佇列的首個阻塞執行緒 如果是是允許獲取到鎖的
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
複製程式碼
Queries whether any threads have been waiting to acquire longer than the current thread.
複製程式碼
hasQueuedPredecessors 方法主要實現的是查詢是否有等待時間超過當前執行緒的其他執行緒, 公平鎖也就是通過該方法保證獲取鎖的有序性。
unlock() - 釋放鎖
公平鎖的釋放與非公平鎖的釋放操作一致
小結
- ReentrantLock 如何實現可重入 ? (通過判斷當前執行緒是否為當前鎖物件的持有者)
- 如何實現公平鎖 ? (若當前同步佇列中有等待的節點則獲取鎖失敗)
- 非公平鎖和公平鎖對效能有什麼影響 ? (公平鎖會造成大量的執行緒切換,非公平鎖會出現執行緒“飢餓”現象,但執行緒切換少提高吞吐量)