1. 簡介
ReentrantLock
,可重入鎖,是一種遞歸無阻塞的同步機制。它可以等同於synchronized
的使用,但是ReentrantLock
提供了比synchronized
更強大、靈活的鎖機制,可以減少死鎖發生的概率。
ReentrantLock
還提供了公平鎖和非公平鎖的選擇,構造方法接受一個可選的公平參數(默認非公平鎖),當設置爲true
時,表示公平鎖,否則爲非公平鎖。公平鎖與非公平鎖的區別在於公平鎖的鎖獲取是有順序的。但是公平鎖的效率往往沒有非公平鎖的效率高,在許多線程訪問的情況下,公平鎖表現出較低的吞吐量。
// 採用公平鎖
ReentrantLock fairLock = new ReentrantLock(true);
// 採用非公平鎖
ReentrantLock unFairLock = new ReentrantLock();
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
public ReentrantLock() {
sync = new NonfairSync();
}
2. 獲取鎖
我們一般都是這麼使用ReentrantLock
獲取鎖的:
//非公平鎖
ReentrantLock lock = new ReentrantLock();
lock.lock();
lock()
方法:
public void lock() {
sync.lock();
}
Sync
爲ReentrantLock
裏面的一個內部類,它繼承AQS
(AbstractQueuedSynchronizer
),它有兩個子類:公平鎖FairSync
和非公平鎖NonfairSync
。
2.1 非公平鎖
final void lock() {
// 首先嚐試通過 CAS 的方式獲取鎖
if (compareAndSetState(0, 1))
// 獲取成功後,將鎖的擁有者設置爲當前線程
setExclusiveOwnerThread(Thread.currentThread());
else
// 獲取失敗,調用AQS的acquire(int arg)方法
acquire(1);
}
首先會第一次嘗試快速獲取鎖,如果獲取失敗,則調用acquire(int arg)
方法,該方法定義在AQS
中,如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
這個方法首先調用tryAcquire(int arg)
方法,在AQS
中,tryAcquire(int arg)
需要自定義同步組件提供實現,非公平鎖實現如下:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
// 獲取當前線程
final Thread current = Thread.currentThread();
// 獲取同步狀態
int c = getState();
// state == 0,表示該鎖未被獲取
if (c == 0) {
// 通過 CAS 獲取鎖成功,設置爲當前線程所有
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;
}
2.2 公平鎖
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;
}
}
2.3 區別
公平鎖與非公平鎖的區別在於獲取鎖的時候是否按照FIFO的順序來。
比較非公平鎖和公平鎖獲取同步狀態的過程,會發現兩者唯一的區別就在於公平鎖在獲取同步狀態時多了一個限制條件:hasQueuedPredecessors()
,定義如下:
public final boolean hasQueuedPredecessors() {
Node t = tail; //尾節點
Node h = head; //頭節點
Node s;
//頭節點 != 尾節點
//同步隊列第一個節點不爲null
//當前線程是同步隊列第一個節點
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
3. 釋放鎖
獲取同步鎖後,使用完畢則需要釋放鎖,ReentrantLock
提供了unlock
釋放鎖:
public void unlock() {
sync.release(1);
}
unlock
內部使用Sync
的release(int arg)
釋放鎖,release(int arg)
是在AQS
中定義的:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
與獲取同步狀態的acquire(int arg)
方法相似,釋放同步狀態的tryRelease(int arg)
同樣是需要自定義同步組件自己實現:
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
4. ReentrantLock與synchronized的區別
- 與
synchronized
相比,ReentrantLock
提供了更多,更加全面的功能,具備更強的擴展性。例如:時間鎖等候,可中斷鎖等候,鎖投票。 ReentrantLock
還提供了條件Condition
,對線程的等待、喚醒操作更加詳細和靈活,所以在多個條件變量和高度競爭鎖的地方,ReentrantLock
更加適合。ReentrantLock
提供了可輪詢的鎖請求。它會嘗試着去獲取鎖,如果成功則繼續,否則可以等到下次運行時處理,而synchronized
則一旦進入鎖請求要麼成功要麼阻塞,所以相比synchronized
而言,ReentrantLock
會不容易產生死鎖些。ReentrantLock
支持更加靈活的同步代碼塊,但是使用synchronized
時,只能在同一個synchronized
塊結構中獲取和釋放。注:ReentrantLock
的鎖釋放一定要在finally
中處理,否則可能會產生嚴重的後果。ReentrantLock
支持中斷處理,且性能較synchronized
會好些。