ReentrantLock可重入鎖,相對synchronized來說是比較輕量級的鎖,內部實現了一個同步器Syn(根據公平和非公平參數又實現了兩個子同步器),根據cas原理設計的鎖。可重入的意思是在當前線程內可重複獲取鎖。
可根據參數設置為公平鎖和非公平鎖,默認非公平鎖。公平鎖和非公平鎖的主要區別是當併發獲取鎖時是否遵循fifo原則。非公平鎖減少了線程間上下文切換的次數從而提高了處理效率,但是這也是假設大部分線程處理時間短的基礎上的,加入線程處理時間較長,就會增加自旋的次數反而造成效率下降,使用時可根據實際情況選擇。
以下以默認的非公平鎖解讀獲取鎖和釋放鎖的過程。
1、lock()
獲取鎖的流程比較複雜,因為大師考慮到各種情況,所以分支和調用鏈比較長,簡化過程如下:
lock()方法代碼如下:
public void lock() {
sync.lock();
}
方法內調用同步器的lock方法,如下:
final void lock() {
//if中獲取鎖
if (compareAndSetState(0, 1))
//獲取成功設置獨佔線程為當前線程(不再單獨講解)
setExclusiveOwnerThread(Thread.currentThread());
else
//獲取失敗處理
acquire(1);
}
先看獲取鎖的方法compareAndSetState(0, 1),代碼如下:
protected final boolean compareAndSetState(int expect, int update) {
//unsafe是java底層的一個本地對象,可以直接讀寫系統內存(不安全),
//compareAndSwapInt調用os底層的方法實現值更新:與期望值一樣則更新,返回新值,
//不一樣返回舊值(此返回值轉為了true/false)
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
再看acquire(1)方法
public final void acquire(int arg) {
//tryAcquire(arg) 再次獲取鎖
if (!tryAcquire(arg) &&
//如果再次獲取失敗把當前線程加入到隊列,阻塞當前線程,等待有釋放的線程
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//如果阻塞過程中有過線程中斷,再補上線程中斷,因為阻塞時清除了中斷標示(好多文章中沒有說明,
//造成對自我中斷有疑惑)
selfInterrupt();
}
接下來看tryAcquire代碼,如下:
protected final boolean tryAcquire(int acquires) {
//直接調用了非公平鎖獲取方法nonfairTryAcquire(acquires),如下:
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
//獲取當前線程
final Thread current = Thread.currentThread();
//獲取鎖狀態
int c = getState();
//如果鎖沒有線程被佔用
if (c == 0) {
//再次獲取鎖,這和公平鎖的區別是公平鎖會判斷是否有等待的線程,如果有直接返回獲取失敗
if (compareAndSetState(0, acquires)) {
//設置獨佔線程為當前線程
setExclusiveOwnerThread(current);
return true;
}
}//如果以後線程獲取鎖且為當前線程,鎖狀態加1(可重入)
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;
}
再看加入阻塞隊列方法addWaiter(Node.EXCLUSIVE),以獨佔方式加到尾部。阻塞隊列是一個雙向鏈表。
private Node addWaiter(Node mode) {
//創建當前線程節點
Node node = new Node(Thread.currentThread(), mode);
//獲取當前尾節點
Node pred = tail;
//如果當前尾節點不為空加入到尾部
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//補充加入,如果當前沒有頭尾節點.....相關的處理
enq(node);
return node;
}
再看acquireQueued(addWaiter(Node.EXCLUSIVE), arg)方法
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) &&
//阻塞並清除中斷標示,對應上邊的“自我了結”(selfInterrupt)
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//當前線程努力獲取鎖時出現了異常或超時一類的,就把當前線程清出隊列
if (failed)
cancelAcquire(node);
}
}
至此獲取鎖的過程解讀完畢。
2、unlock()
釋放鎖的過程相對獲取鎖較簡單一些,其流程如下:
unlock()代碼如下:
public void unlock() {
//直接調用同步器的釋放方法
sync.release(1);
}
//同步器釋放方法
public final boolean release(int arg) {
if (tryRelease(arg)) {
//釋放成功,判斷是否有等待隊列,如果有喚醒後繼節點
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
//返回true
return true;
}
//注意:這個失敗ReentrantLock自己消化了,併為告訴調用者結果,其實調用者也不需要關心
return false;
}
再看釋放鎖的方法:tryRelease(arg)
protected final boolean tryRelease(int releases) {
//當前鎖狀態-1,可重入鎖可能獲取了多次
int c = getState() - releases;
//當前線程非當前獨佔線程,扔個exception
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
//默認沒有釋放完成
boolean free = false;
//如果已經全部釋放完成,清除當前獨佔線程,set(null)
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//設置鎖狀態
setState(c);
//返回是否最終釋放狀態
return free;
}
再看等待隊列頭節點的方法unparkSuccessor(h):
private void unparkSuccessor(Node node) {
//獲取頭節點的等待狀態
int ws = node.waitStatus;
//如果為等待狀態,清除等待狀態,set(0)
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//獲取頭節點的下一個節點:繼任節點
Node s = node.next;
//如果繼任節點為空或者等待狀態為已取消,找下下一個。
if (s == null || s.waitStatus > 0) {
s = null;
//從尾向頭找(本人覺得從頭向尾找應該更快些)
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//如果繼任節點不為空,喚醒繼任節點(此處喚醒的非頭節點,因為頭節點是個空節點)
if (s != null)
LockSupport.unpark(s.thread);
}
至此釋放鎖的過程解讀完畢。
閱讀更多 像我這樣的人優秀的人 的文章