ReentrantLock是一個可重入的鎖,內部採用AQS來實現
下面我會對其源碼進行詳細的分析,因爲貼了源碼,所以我會直接在源碼上進行註釋,並加入一些我自己的理解,以求大家可以更容易的理解。廢話不多說,直接開始:
ReentrantLock的構造函數:
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
可以看到構造函數做了一件事,就是將lock實例的sync字段進行了初始化,從代碼可以看出根據參數的不同,將sync初始化爲不同的實現,即公平鎖FairSync和非公平鎖NonfairSync,那sync到底是什麼呢?
abstract static class Sync extends AbstractQueuedSynchronizer {}
具體的代碼就不粘出來了,可以看到Sync繼承自AbstractQueuedSynchronizer抽象隊列同步器AQS,也就是說ReentrantLock的真正實現其實是依賴了AQS,下面就對AQS進行分析
首先看一下AQS中比較重要的屬性
exclusiveOwnerThread:獨佔鎖線程,指向了當前獲取到鎖的線程
state:AQS的核心,AQS就是用這個字段來實現鎖的獲取和重入,在沒有線程獲取到鎖的時候,鎖的狀態爲0,獲取的時候,通過cas對其進行+1,並且每重入一次再 +1,釋放一次 -1,具體的後面代碼展示
head,tail:AQS維護了一個內部類Node的雙向隊列,由未獲取到鎖的線程包裝成的Node節點組成,也就是獲取鎖失敗加入隊列尾部。
AQS的獲取鎖過程分析
線程通過CAS將state從0設置爲1,如果設置成功,說明獲取鎖成功,並且將exclusiveOwnerThread指向自己;
當調用lock方法時,公平鎖和非公平鎖的實現有區別
非公平鎖的實現:線程會直接進行CAS操作去設置state,如果成功就獲取到鎖,如果失敗,繼續調用NonFairSync的acquire(1)方法
公平鎖的實現:線程會直接調用FairSync的accquire(1)方法,這兩個方法都調用了AQS的方法
public final void acquire(int arg) {
//第一步嘗試獲取鎖
//如果成功了,就可以執行自身的邏輯了
//如果失敗了,執行下一步addWaiter()方法,也就是將自身線程包裝成Node加入到隊列中
//第三步 死循環獲取鎖,如果失敗則將線程掛起
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
這裏需要看一下tryAcquire方法,首先是非公平鎖
final boolean nonfairTryAcquire(int acquires) {
//獲取當前線程
final Thread current = Thread.currentThread();
//獲取AQS的狀態
int c = getState();
//如果等於0,說明沒有線程獲取到鎖,通過CAS嘗試獲取鎖,跟一開始的邏輯一模一樣
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
//如果不等於0,說明有線程已經獲取到鎖了,這裏判斷AQS的獨佔鎖指針是不是指向自己
//如果是,說明是重入,這裏就是重入的實現
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;
}
下面看一下公平鎖的實現
protected final boolean tryAcquire(int acquires) {
//前面的邏輯都一樣
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
//差別就在這裏,多了一個hasQueuedPredecessors方法,這是跟非公平鎖唯一的區別
//也就是爲什麼叫公平鎖體現在這裏。
//這裏判斷有沒有別的線程比他更早來,如果返回true說明有,肯定就直接獲取失敗了
//如果沒有別他更早來的線程,那麼自己就可以去嘗試獲取鎖了
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;
}
下面看一下hasQueuedPredecessors方法
public final boolean hasQueuedPredecessors() {
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
//這裏就是幹了這樣一件事,判斷頭節點的下一個節點是不是自己
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
總結一下,AQS中維護了一堆wait線程組成的等待隊列,凡是進入了這個隊列的線程,之後就會按順序一個一個獲取到鎖,執行邏輯,也就是將他們串行化了,那麼公平和非公平體現在什麼地方呢?
就是tryAcquire這裏,公平鎖是說新線程進來對於隊列中的線程是公平的,如果隊列中有等待線程,它就直接往後排,而非公平鎖是,新線程對於隊列中的等待線程是不公平的,可能存在隊列中的頭節點釋放掉鎖之後喚醒下一個線程,結果有一個新的線程進來同時獲取鎖,這個時候他們機會是平等的,因此說這是非公平鎖。
下面看一下addWaiter方法
private Node addWaiter(Node mode) {
//將當前線程封裝成node
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
//如果tail不爲空,也就是隊列中已經有一些等待線程了,直接加入尾部後返回即可。
if (pred != null) {
node.prev = pred;
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//如果tail爲空,就會走到這裏來
enq(node);
return node;
}
private Node enq(final Node node) {
//死循環,爲什麼死循環呢?往下看
for (;;) {
//拿到tail,第一次循環這裏肯定是null
Node t = tail;
if (t == null) { // Must initialize
//第一次進來,將一個空的節點作爲頭和尾節點,看一下Node的構造函數和屬性
//可以發現頭節點的waitstatus=0
if (compareAndSetHead(new Node()))
tail = head;
} else {
//第二次進來就到了這裏
node.prev = t;
//將node節點插入到隊列的最後
//這裏爲什麼這麼做呢?向下看
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
解答上面的問題,能這麼設計就有他的原因,看一下死循環獲取鎖的方法
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
//這裏是死循環,是核心
for (;;) {
//獲取node節點的prev節點,如果沒有直接報錯
final Node p = node.predecessor();
//判斷p是不是頭節點,如果是,嘗試獲取鎖
//如果能夠成功,說明head要麼取消,要麼已經釋放了鎖
//所以將當前節點設置爲頭節點,也就是說刪除頭節點的操作是在這裏執行的
//並不是在釋放鎖的時候執行的
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 將node的狀態改爲singal之後掛起,等待有線程喚醒他之後
// 就可以繼續循環獲取鎖了
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
//這裏就是線程可能會被中斷,如果中斷了就從隊列中去除掉
if (failed)
cancelAcquire(node);
}
}
//node節點獲取鎖失敗就會進入這個方法,第一次進入的時候,pred是頭節點,並且ws=0
//這個方法的源碼先看else塊,再看第一個if,再看第二個if
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
//第二次走這裏
//直接返回true,那麼就會調用park方法,線程阻塞
return true;
if (ws > 0) {
//這裏就是當node的前置節點不是頭節點並且狀態是已取消狀態
//那麼就是去找下一個正常的前置節點,這裏就是維護一下鏈表的關係
//之後return false 同樣是要進行二次循環
/*
* Predecessor was cancelled. Skip over predecessors and
* indicate retry.
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/*
* waitStatus must be 0 or PROPAGATE. Indicate that we
* need a signal, but don't park yet. Caller will need to
* retry to make sure it cannot acquire before parking.
*/
//第一次進來走這裏,把頭節點的ws設置爲-1,然後return false
//也就是外面的循環邏輯會走第二遍,那麼第二遍進來的時候,頭節點的ws已經是-1了
//就會走第一個if
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
釋放鎖的原理
public void unlock() {
sync.release(1);
}
public final boolean release(int arg) {
//嘗試釋放鎖,成功之後,準備喚醒頭節點的下一個正常節點
if (tryRelease(arg)) {
Node h = head;
//如何代碼沒有看仔細,會對頭節點的waitStatus狀態有疑惑
//以爲頭節點是一個空姐點,初始狀態爲0
//但是經過了shouldParkAfterFailedAcquire方法之後,狀態就是變成-1
//所以這裏是true,正常進行釋放操作
if (h != null && h.waitStatus != 0)
//這個方法就是真正的喚醒頭節點的下一個正常的節點
unparkSuccessor(h);
return true;
}
return false;
}
protected final boolean tryRelease(int releases) {
//重入性質
int c = getState() - releases;
//如果當前線程不是AQS的獨佔鎖線程直接報錯
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
//直到全部釋放完成,將獨佔鎖線程設置爲null,釋放成功
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
//重入鎖就一直 -- ,然後CAS設置狀態就好了
setState(c);
return free;
}
private void unparkSuccessor(Node node) {
//獲取頭節點的狀態
int ws = node.waitStatus;
if (ws < 0)
//這裏將頭節點的狀態變成0
//等待後置節點將其替換
compareAndSetWaitStatus(node, ws, 0);
// 獲取頭節點的下一個節點
Node s = node.next;
// s如果是null,或者狀態是已取消的狀態,就從尾部獲取下一個正常的可以喚醒的節點
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;
}
// 通過上面的計算獲取到的節點,在這裏進行喚醒
// 存在當前node既是head也是tail的情況,這裏的thread就是null
if (s != null)
LockSupport.unpark(s.thread);
}
整個ReentrantLock的源碼分析就是這樣,筆者在看了這些源碼大概幾十次之後(但是都是粗略看的,慚愧,認真看也就是兩次,一次是開始寫這篇文章,另外一次就是修改第一次寫的時候一些錯誤的地方),終於看懂了,其實不是很難,相反理解之後反而覺得非常之簡單,自身的收穫也很大。歡迎大家對我的分析進行指正,大家一起進步!!!
原創不易,且行且珍惜。