java可重入鎖(ReentrantLock)源碼解讀

ReentrantLock可重入鎖,相對synchronized來說是比較輕量級的鎖,內部實現了一個同步器Syn(根據公平和非公平參數又實現了兩個子同步器),根據cas原理設計的鎖。可重入的意思是在當前線程內可重複獲取鎖。

可根據參數設置為公平鎖和非公平鎖,默認非公平鎖。公平鎖和非公平鎖的主要區別是當併發獲取鎖時是否遵循fifo原則。非公平鎖減少了線程間上下文切換的次數從而提高了處理效率,但是這也是假設大部分線程處理時間短的基礎上的,加入線程處理時間較長,就會增加自旋的次數反而造成效率下降,使用時可根據實際情況選擇。

以下以默認的非公平鎖解讀獲取鎖和釋放鎖的過程。

1、lock()

獲取鎖的流程比較複雜,因為大師考慮到各種情況,所以分支和調用鏈比較長,簡化過程如下:

java可重入鎖(ReentrantLock)源碼解讀

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()

釋放鎖的過程相對獲取鎖較簡單一些,其流程如下:

java可重入鎖(ReentrantLock)源碼解讀

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);

}

至此釋放鎖的過程解讀完畢。


分享到:


相關文章: