ReentrantLock
是一個和synchronized
擁有相同語義但同時擴展了額外功能的可重入互斥鎖實現。ReentrantLock
將由最近成功得到鎖定,而且尚未釋放該鎖定的線程所擁有。當鎖定沒有被另外一個線程所擁有時,調用 lock()
的線程將成功獲取該鎖定並返回。若是當前線程已經擁有該鎖定,此方法將當即返回。可使用isHeldByCurrentThread()
和 getHoldCount()
方法來檢查此狀況是否發生。node
ReentrantLock
有公平鎖和非公平鎖兩種,經過構造器傳入一個boolean fair
參數指定,該參數是可選的,默認爲false,也就是說,默認是非公平鎖實現。但請注意,這裏所說的公平與非公平,只是說獲取鎖的時候是否順序進行,並不保證線程調度的公平性。所以,使用公平鎖的多個線程中的一個可能會連續屢次得到它。公平鎖即在線程相互爭用鎖的狀況下,它會更偏向於讓等待時間最長的那個線程得到鎖(隊頭),但相比較非公平鎖,使用多個線程訪問公平鎖的程序吞吐量比較低,或者明顯更慢。bash
一般狀況下建議將釋放鎖的操做放置在finally{}
語句塊中,以下面代碼:併發
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
複製代碼
查看ReentrantLock
源碼,發現該類只有一個Sync
的成員變量:jvm
private final Sync sync;
複製代碼
Sync
爲繼承自AQS
的一個同步器實現,其內部同時提供了一個lock()
抽象方法供子類實現,完成獲取鎖操做。Sync
有FairSync
,NonfairSync
兩個子類,分別提供公平鎖和非公平鎖的相關操做。高併發
abstract static class Sync extends AbstractQueuedSynchronizer {
abstract void lock();
}
static final class FairSync extends Sync {}
static final class NonfairSync extends Sync {}
複製代碼
下面分別從公平和非公平兩種實現探討其獲取鎖和釋放鎖的操做:源碼分析
final void lock() {
acquire(1);
}
複製代碼
能夠看出其直接調用AQS
的acquire(int)
方法獲取鎖,接下來看下acquire()
實現:性能
public final void acquire(int arg) {
// 一、首先嚐試獲取鎖,若是獲取失敗,那麼就假如等待隊列中
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
複製代碼
接着查看FairSync
中提供的tryAcquire(int)
方法:優化
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
// 一、若是鎖尚未被別人獲取,及同步狀態爲0
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步能夠看出,公平鎖會偏向於給隊列中等待時間最長的線程優先得到鎖,若是此時沒有其餘線程在等待,則執行CAS爭搶鎖資源。第3步若是該鎖已經被持有,則判斷持有該鎖的線程是否當前線程自己,若是是,那麼同步狀態state遞增(加鎖次數)。能夠看出,公平鎖按等待隊列順序分配鎖資源,高併發下性能,效率不高。ui
final void lock() {
// 一、直接參與競爭,若是該鎖已被其它線程持有,那麼就執行else
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
複製代碼
接着繼續查看Sync
提供的nonfairTryAcquire()
方法,源碼以下:spa
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 一、無需額外判斷是否有其它線程處於等待狀態
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()
方法怎麼和上面講到的tryAcquire(int)
有點類似,原來tryAcquire(int)
比nonfairTryAcquire()
多了一步判斷同步隊列中是否有其它線程正在等待,所以,這也是公平和非公平的區別所在,若是nonfairTryAcquire()
方法也沒能獲取鎖,那麼將被掛到同步等待隊列中。
那麼,咱們可能會問,既然會被掛到同步隊列中,那當前被掛起的這個線程後續是怎麼被喚醒搶奪鎖資源的呢?仍是按順序出隊列嗎?若是仍是按順序出隊列,是否是就和公平鎖同樣了呢?其實很簡單,仍是回到下面這個方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
複製代碼
首先執行tryAcquire(arg)
獲取鎖失敗以後,會執行addWaiter()
將本身封裝成Node
節點入隊,接着調用acquireQueued()
方法,答案就在acquireQueued()
方法中,源碼以下:
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
// 一、自旋,其餘參與同一個鎖競爭的線程也在同時不斷自旋
for (;;) {
final Node p = node.predecessor();
// 二、不斷嘗試獲取鎖資源
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 三、處理中斷狀況
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
// 因爲中斷或者超時,必須將狀態改爲cancel
if (failed)
cancelAcquire(node);
}
}
複製代碼
原來非公平鎖參與鎖競爭失敗、被掛到等待隊列以後,會cas+自旋直到獲取鎖成功,確實很不公平,哈哈。
public void unlock() {
sync.release(1);
}
複製代碼
ReentrantLock
中釋放鎖統一爲unlock()
方法,從上面源碼能夠看出,每調用一次unlock()
方法,同步狀態就會減一,也就是說,lock()
多少次,就要對應unlock()
多少次。
接着深刻release()
方法,源碼以下:
public final boolean release(int arg) {
// 一、嘗試釋放鎖
if (tryRelease(arg)) {
Node h = head;
// 二、喚醒那些因爲中斷或其它狀況致使waitStatus不爲0的節點
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
複製代碼
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
// 若是線程不是當前線程持有,那麼就報錯
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
// 該鎖已經沒有線程持有了,同步狀態爲0了,那麼其它線程就能夠從cas+自旋中退出了
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
複製代碼
都說synchronized
使用的是重量級鎖,性能很是低下,可是新版本的jdk已經作了如偏向鎖之類的優化,性能其實還能夠。synchronized
屬於jvm層面的加鎖機制,而Lock
屬於API層面上的加鎖,那麼它們到底有什麼區別呢?
一、如等待可中斷
持有鎖的線程若是長期不釋放鎖,正在等待的線程能夠選擇放棄等待。
1.設置超時方法tryLock(long timeout, TimeUnit unit)
,時間過了就放棄等待;
2.調用lockInterruptibly()
方法,若是線程中斷了,則結束獲取鎖操做;
二、synchronized
爲非公平鎖,ReentrantLock
同時支持公平鎖和非公平鎖
三、ReentrantLock
可結合Condition
條件進行使用,可分別對多種條件加鎖,對線程的等待、喚醒操做更加詳細和靈活,在多個條件變量和高度競爭鎖的地方,ReentrantLock
更加適合
有一點須要注意就是,釋放鎖的操做必定要在finally
塊執行,不然可能出現死鎖等意外狀況。
參考文章: