在Java併發包java.util.concurrent中能夠看到,很多源碼是基於AbstractQueuedSynchronizer(如下簡寫AQS)這個抽象類,由於它是Java併發包的基礎工具類,是實現ReentrantLock、CountDownLatch、Semaphore、FutureTask 等類的基礎。
AQS的主要使用方式是繼承,子類經過繼承AQS並實現它的抽象方法來管理同步狀態,在抽象方法中免不了要對同步狀態進行更改,這時就須要使用AQS提供的3個方法(getState()、setState(int newState)和compareAndSetState(int expect,int update) 來進行操做,由於他們可以保證狀態的改變是安全的 。java
在 AQS 內部,經過維護一個FIFO隊列來管理多線程的排隊工做。在公平競爭的狀況下,沒法獲取同步狀態的線程將會被封裝成一個節點,置於隊列尾部。入隊的線程將會經過自旋的方式獲取同步狀態,若在有限次的嘗試後,仍未獲取成功,線程則會被阻塞住。大體示意圖以下:
當頭結點釋放同步狀態後,且後繼節點對應的線程被阻塞,此時頭結點線程將會去喚醒後繼節點線程。後繼節點線程恢復運行並獲取同步狀態後,會將舊的頭結點從隊列中移除,並將本身設爲頭結點。大體示意圖以下: node
先來看看 AQS 有哪些變量,搞清楚這些基本就知道 AQS 是什麼套路了,畢竟能夠猜嘛!安全
// 頭結點,你直接把它當作 當前持有鎖的線程 多是最好理解的
private transient volatile Node head;
// 阻塞的尾節點,每一個新的節點進來,都插入到最後,也就造成了一個隱視的鏈表
private transient volatile Node tail;
// 這個是最重要的,不過也是最簡單的,表明當前鎖的狀態,0表明沒有被佔用,大於0表明有線程持有當前鎖
// 之因此說大於0,而不是等於1,是由於鎖能夠重入嘛,每次重入都加上1
private volatile int state;
// 表明當前持有獨佔鎖的線程,舉個最重要的使用例子,由於鎖能夠重入
// reentrantLock.lock()能夠嵌套調用屢次,因此每次用這個來判斷當前線程是否已經擁有了鎖
// if (currentThread == getExclusiveOwnerThread()) {state++}
private transient Thread exclusiveOwnerThread; //繼承自AbstractOwnableSynchronizer
在併發的狀況下,AQS 會將未獲取同步狀態的線程將會封裝成節點,並將其放入同步隊列尾部。同步隊列中的節點除了要保存線程,還要保存等待狀態。無論是獨佔式仍是共享式,在獲取狀態失敗時都會用到節點類。因此這裏咱們要先看一下節點類的實現,爲後面的源碼分析進行簡單鋪墊。既然AQS經過一個雙向鏈表來維護全部的的節點,那麼先看一下每個節點的結構:數據結構
static final class Node {
/** Marker to indicate a node is waiting in shared mode */
// 標識節點當前在共享模式下
static final Node SHARED = new Node();
/** Marker to indicate a node is waiting in exclusive mode */
// 標識節點當前在獨佔模式下
static final Node EXCLUSIVE = null;
// ======== 下面的幾個int常量是給waitStatus用的 ===========
/** waitStatus value to indicate thread has cancelled */
// 代碼此線程取消了爭搶這個鎖
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
// 官方的描述是,其表示當前node的後繼節點對應的線程須要被喚醒
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
// 本文不分析condition,因此略過吧,下一篇文章會介紹這個
static final int CONDITION = -2;
/**
* waitStatus value to indicate the next acquireShared should
* unconditionally propagate
*/
// 一樣的不分析,略過吧
static final int PROPAGATE = -3;
// =====================================================
// 取值爲上面的一、-一、-二、-3,或者0(之後會講到)
// 這麼理解,暫時只須要知道若是這個值 大於0 表明此線程取消了等待,
// 也許就是說半天搶不到鎖,不搶了,ReentrantLock是能夠指定timeouot的。。。
volatile int waitStatus;
// 前驅節點的引用
volatile Node prev;
// 後繼節點的引用
volatile Node next;
// 這個就是線程本尊
volatile Thread thread;
}
Node 的數據結構其實也挺簡單的,就是 thread + waitStatus + pre + next 四個屬性而已。多線程
本節將介紹三組重要的方法,經過使用這三組方法便可實現一個同步組件。併發
第一組方法是用於訪問/設置同步狀態的,以下:app
方法 | 描述 |
---|---|
int getState() | 獲取同步狀態 |
void setState() | 設置同步狀態 |
boolean compareAndSetState(int expect, int update) | 經過 CAS 設置同步狀態 |
第二組方須要由同步組件覆寫。以下:less
方法 | 描述 |
---|---|
boolean tryAcquire(int arg) | 獨佔式獲取同步狀態 |
boolean tryRelease(int arg) | 獨佔式釋放同步狀態 |
int tryAcquireShared(int arg) | 共享式獲取同步狀態 |
boolean tryReleaseShared(int arg) | 共享式私房同步狀態 |
boolean isHeldExclusively() | 檢測當前線程是否獲取獨佔鎖 |
第三組方法是一組模板方法,同步組件可直接調用。以下:ide
方法 | 描述 |
---|---|
void acquire(int arg) | 獨佔式獲取同步狀態,該方法將會調用 tryAcquire 嘗試獲取同步狀態。獲取成功則返回,獲取失敗,線程進入同步隊列等待。 |
void acquireInterruptibly(int arg) | 響應中斷版的 acquire |
boolean tryAcquireNanos(int arg,long nanos) | 超時+響應中斷版的 acquire |
void acquireShared(int arg) | 共享式獲取同步狀態,同一時刻可能會有多個線程得到同步狀態。好比讀寫鎖的讀鎖就是就是調用這個方法獲取同步狀態的。 |
void acquireSharedInterruptibly(int arg) | 響應中斷版的 acquireShared |
boolean tryAcquireSharedNanos(int arg,long nanos) | 超時+響應中斷版的 acquireShared |
boolean release(int arg) | 獨佔式釋放同步狀態 |
boolean releaseShared(int arg) | 共享式釋放同步狀態 |
ReentrantLock 在內部用了內部類 Sync 來管理鎖,因此真正的獲取鎖和釋放鎖是由 Sync 的實現類來控制的。
Sync 有兩個實現,分別爲 NonfairSync(非公平鎖)和 FairSync(公平鎖),咱們看 NonfairSync 部分。工具
加鎖的一步以下:
public void lock() {
sync.lock();
}
對於非公平鎖,則執行以下流程:
final void lock() {
// 若是鎖沒有被任何線程鎖定且加鎖成功則設定當前線程爲鎖的擁有者
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
1)咱們假設在這個時候,尚未任務線程獲取鎖,這個時候,第一個線程過來了(咱們使用的是非公平鎖),那麼第一個線程thread1會去獲取鎖,這時它會調用下面的方法,經過CAS的操做,將當前AQS的state由0變成1,證實當前thread1已經獲取到鎖,而且將AQS的exclusiveOwnerThread設置成thread1,證實當前持有鎖的線程是thread1。
2)若是此時來了第二個線程thread2,而且咱們假設thread1尚未釋放鎖,由於咱們使用的是非公平鎖,那麼thread2首先會進行搶佔式的去獲取鎖,調用NonFairSync.lock方法獲取鎖。NonFairSync.lock方法的第一個分支是經過CAS操做獲取鎖,很明顯,這一步確定會失敗,由於此時thread1尚未釋放鎖。那麼thread2將會走NonFairSync.lock方法的第二個分支,進行acquire(1)操做。acquire(1)實際上是AQS的方法,acquire(1)方法內部首先調用tryAcquire方法,ReentrantLock.NonFairLock重寫了tryAcquire方法,而且ReentrantLock.NonFairLock的tryAcquire方法又調用了ReentrantLock.Sync的nonfairTryAcquire方法,nonfairTryAcquire方法以下:
// 該方法來自父類AQS,我直接貼過來這邊,下面分析的時候一樣會這樣作,不會給讀者帶來閱讀壓力
// 咱們看到,這個方法,若是tryAcquire(arg) 返回true, 也就結束了。
// 不然,acquireQueued方法會將線程壓到隊列中
public final void acquire(int arg) {
// 首先調用tryAcquire(1)一下,名字上就知道,這個只是試一試
// 由於有可能直接就成功了呢,也就不須要進隊列排隊了,
// 對於公平鎖的語義就是:原本就沒人持有鎖,根本不必進隊列等待(又是掛起,又是等待被喚醒的)
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
//直接調用下面方法
return nonfairTryAcquire(acquires);
}
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
// 嘗試直接獲取鎖,返回值是boolean,表明是否獲取到鎖
// 返回true:1.沒有線程在等待鎖;2.重入鎖,線程原本就持有鎖,也就能夠理所固然能夠直接獲取
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()) {
// 會進入這個else if分支,說明是重入了,須要操做:state=state+1
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
// 若是到這裏,說明前面的if和else if都沒有返回true,說明沒有獲取到鎖
// 回到上面一個外層調用方法繼續看:
// if (!tryAcquire(arg)
// && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// selfInterrupt();
return false;
}
nonfairTryAcquire方法的執行邏輯以下:
1. 獲取當前將要去獲取鎖的線程,在此時的狀況下,也就是咱們的thread2線程。
2. 獲取當前AQS的state的值。若是此時state的值是0,那麼咱們就經過CAS操做獲取鎖,而後設置AQS的exclusiveOwnerThread爲thread2。很明顯,在當前的這個執行狀況下,state的值是1不是0,由於咱們的thread1尚未釋放鎖。
3. 若是當前將要去獲取鎖的線程等於此時AQS的exclusiveOwnerThread的線程,則此時將state的值加1,很明顯這是重入鎖的實現方式。在此時的運行狀態下,將要去獲取鎖的線程不是thread1,也就是說這一步不成立。
4. 以上操做都不成立的話,咱們直接返回false。
既然返回了false,那麼以後就會調用addWaiter方法,這個方法負責把當前沒法獲取鎖的線程包裝爲一個Node添加到隊尾。經過下面的代碼片斷咱們就知道調用邏輯:
// 假設tryAcquire(arg) 返回false,那麼代碼將執行:
// acquireQueued(addWaiter(Node.EXCLUSIVE), arg),
// 這個方法,首先須要執行:addWaiter(Node.EXCLUSIVE)
/**
* Creates and enqueues node for current thread and given mode.
*
* @param mode Node.EXCLUSIVE for exclusive, Node.SHARED for shared
* @return the new node
*/
// 此方法的做用是把線程包裝成node,同時進入到隊列中
// 參數mode此時是Node.EXCLUSIVE,表明獨佔模式
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
// Try the fast path of enq; backup to full enq on failure
//如下幾行代碼想把當前node加到鏈表的最後面去,也就是進到阻塞隊列的最後
Node pred = tail;
// tail!=null => 隊列不爲空(tail==head的時候,其實隊列是空的,不過無論這個吧)
if (pred != null) {
// 設置本身的前驅 爲當前的隊尾節點
node.prev = pred;
// 用CAS把本身設置爲隊尾, 若是成功後,tail == node了
if (compareAndSetTail(pred, node)) {
// 進到這裏說明設置成功,當前node==tail, 將本身與以前的隊尾相連,
// 上面已經有 node.prev = pred
// 加上下面這句,也就實現了和以前的尾節點雙向鏈接了
pred.next = node;
// 線程入隊了,能夠返回了
return node;
}
// 仔細看看上面的代碼,若是會到這裏,
// 說明 pred==null(隊列是空的) 或者 CAS失敗(有線程在競爭入隊)
// 讀者必定要跟上思路,若是沒有跟上,建議先不要往下讀了,往回仔細看,不然會浪費時間的
enq(node);
return node;
}
很明顯在addWaiter內部:
第一步:將當前將要去獲取鎖的線程也就是thread2和獨佔模式封裝爲一個node對象。而且咱們也知道在當前的執行環境下,線程阻塞隊列是空的,由於thread1獲取了鎖,thread2也是剛剛來請求鎖,因此線程阻塞隊列裏面是空的。很明顯,這個時候隊列的尾部tail節點也是null,那麼將直接進入到enq方法。
第二步:咱們首先看下enq方法的內部實現。首先內部是一個自旋循環。
/**
* Inserts node into queue, initializing if necessary. See picture above.
* @param node the node to insert
* @return node's predecessor
*/
// 採用自旋的方式入隊
// 以前說過,到這個方法只有兩種可能:等待隊列爲空,或者有線程競爭入隊,
// 自旋在這邊的語義是:CAS設置tail過程當中,競爭一次競爭不到,我就屢次競爭,總會排到的
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 以前說過,隊列爲空也會進來這裏
if (t == null) { // Must initialize
// 初始化head節點
// 細心的讀者會知道原來head和tail初始化的時候都是null,反正我不細心
// 仍是一步CAS,你懂的,如今多是不少線程同時進來呢
if (compareAndSetHead(new Node()))
// 給後面用:這個時候head節點的waitStatus==0, 看new Node()構造方法就知道了
// 這個時候有了head,可是tail仍是null,設置一下,
// 把tail指向head,放心,立刻就有線程要來了,到時候tail就要被搶了
// 注意:這裏只是設置了tail=head,這裏可沒return哦,沒有return,沒有return
// 因此,設置完了之後,繼續for循環,下次就到下面的else分支了
tail = head;
} else {
// 下面幾行,和上一個方法 addWaiter 是同樣的,
// 只是這個套在無限循環裏,反正就是將當前線程排到隊尾,有線程競爭的話排不上重複排
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
第一次循環:t爲null,隨後咱們new出了一個空的node節點,而且經過CAS操做設置了線程的阻塞隊列的head節點就是咱們剛纔new出來的那個空的node節點,其實這是一個「假節點」,那麼什麼是「假節點」呢?那就是節點中不包含線程。設置完head節點後,同時又將head節點賦值給尾部tail節點,到此第一次循環結束。此時的節點就是以下:
第二次循環:如今判斷維度tail已經不是null了,那麼就走第二個分支了,將尾部tail節點賦值給咱們傳遞進來的節點Node的前驅節點,此時的結構以下:
而後再經過CAS的操做,將咱們傳遞進來的節點node設置成尾部tail節點,而且將咱們的node節點賦值給原來的老的尾部節點的後繼節點,此時的結構以下:
這個時候代碼中使用了return關鍵字,也就是證實咱們通過了2次循環跳出了這個自懸循環體系。
在執行addWaiter將節點加入阻塞隊列後,接下來將會調用acquireQueued方法,主要是判斷當前節點的前驅節點是否是head節點,若是是的話,就再去嘗試獲取鎖,若是不是,就掛起當前線程。這裏可能有人疑問了,爲何判斷當前節點的前驅節點是head節點的話就去嘗試獲取鎖呢?由於咱們知道head節點是一個假節點,若是當前的節點的前驅節點是頭節點便是假節點的話,那麼這個假節點的後繼節點就有可能有獲取鎖的機會,因此咱們須要去嘗試。
如今咱們看下acquireQueued方法內部,咱們也能夠清楚的看到,這個方法的內部也是一個自懸循環。:
// 如今,又回到這段代碼了
// if (!tryAcquire(arg)
// && acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
// selfInterrupt();
// 下面這個方法,參數node,通過addWaiter(Node.EXCLUSIVE),此時已經進入阻塞隊列
// 注意一下:若是acquireQueued(addWaiter(Node.EXCLUSIVE), arg))返回true的話,
// 意味着上面這段代碼將進入selfInterrupt(),因此正常狀況下,下面應該返回false
// 這個方法很是重要,應該說真正的線程掛起,而後被喚醒後去獲取鎖,都在這個方法裏了
/**
* Acquires in exclusive uninterruptible mode for thread already in
* queue. Used by condition wait methods as well as acquire.
*
* @param node the node
* @param arg the acquire argument
* @return {@code true} if interrupted while waiting
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
// p == head 說明當前節點雖然進到了阻塞隊列,可是是阻塞隊列的第一個,由於它的前驅是head
// 注意,阻塞隊列不包含head節點,head通常指的是佔有鎖的線程,head後面的才稱爲阻塞隊列
// 因此當前節點能夠去試搶一下鎖
// 這裏咱們說一下,爲何能夠去試試:
// 首先,它是隊頭,這個是第一個條件,其次,當前的head有多是剛剛初始化的node,
// enq(node) 方法裏面有提到,head是延時初始化的,並且new Node()的時候沒有設置任何線程
// 也就是說,當前的head不屬於任何一個線程,因此做爲隊頭,能夠去試一試,
// tryAcquire已經分析過了, 忘記了請往前看一下,就是簡單用CAS試操做一下state
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
// 到這裏,說明上面的if分支沒有成功,要麼當前node原本就不是隊頭,
// 要麼就是tryAcquire(arg)沒有搶贏別人,繼續往下看
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
第一次循環:獲取咱們傳入node的前驅節點,判斷是不是head節點,如今咱們的狀態是:
很明顯知足當前node節點的前驅節點是head節點,那麼如今咱們就要去調用tryAcquire方法,也就是NonfairSync類的tryAcquire方法,而這個方法又調用了ReentrantLock.Sync.nonfairTryAcquire方法。
很明顯thread1佔用鎖,因此thread2獲取鎖是失敗的,直接返回false。按照調用流程,如今進入了當前節點的前驅節點的shouldParkAfterFailedAcquire方法,檢查當前節點的前驅節點的waitstatus。shouldParkAfterFailedAcquire方法內部以下:
/**
* Checks and updates status for a node that failed to acquire.
* Returns true if thread should block. This is the main signal
* control in all acquire loops. Requires that pred == node.prev.
*
* @param pred node's predecessor holding status
* @param node the node
* @return {@code true} if thread should block
*/
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
// 前驅節點的 waitStatus == -1 ,說明前驅節點狀態正常,當前線程須要掛起,直接能夠返回true
if (ws == Node.SIGNAL)
/*
* This node has already set status asking a release
* to signal it, so it can safely park.
*/
return true;
// 前驅節點 waitStatus大於0 ,以前說過,大於0 說明前驅節點取消了排隊。這裏須要知道這點:
// 進入阻塞隊列排隊的線程會被掛起,而喚醒的操做是由前驅節點完成的。
// 因此下面這塊代碼說的是將當前節點的prev指向waitStatus<=0的節點,
// 簡單說,就是爲了找個好爹,由於你還得依賴它來喚醒呢,若是前驅節點取消了排隊,
// 找前驅節點的前驅節點作爹,往前循環總能找到一個好爹的
if (ws > 0) {
/*
* 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.
*/
// 仔細想一想,若是進入到這個分支意味着什麼
// 前驅節點的waitStatus不等於-1和1,那也就是隻多是0,-2,-3
// 在咱們前面的源碼中,都沒有看到有設置waitStatus的,因此每一個新的node入隊時,waitStatu都是0
// 用CAS將前驅節點的waitStatus設置爲Node.SIGNAL(也就是-1)
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
上面的流程以下:
1 若是當前節點的前驅節點的waitStatus=-1,就直接返回true;
2 若是當前節點的前驅節點的waitStatus>0,也就是前驅節點被取消了,那麼就從阻塞隊列中刪除前驅節點;
3 若是以上狀況都不知足,則經過CAS操做將前驅節點設置爲-1(SIGNAL)。
此時,阻塞隊列中,會將head節點的waitStatus由0變爲-1(初始化節點的waitStatus都是0),而後返回false;
而後回到acquireQueued執行第二次循環:很明顯知足當前node節點的前驅節點是head節點,那麼如今咱們就要去調用tryAcquire方法,也就是NonfairSync類的tryAcquire方法,而這個方法又調用了ReentrantLock.Sync.nonfairTryAcquire方法。很明顯此時thread2獲取鎖是失敗的,直接返回false。按照調用流程,如今進入了當前節點的前驅節點的shouldParkAfterFailedAcquire方法,檢查當前節點的前驅節點的waitstatus。此時waitstatus爲-1,這個方法返回true。shouldParkAfterFailedAcquire返回true後,就會調用parkAndCheckInterrupt方法,直接將當前線程thread2中斷。
// 1. 若是shouldParkAfterFailedAcquire(p, node)返回true,
// 那麼須要執行parkAndCheckInterrupt():
// 這個方法很簡單,由於前面返回true,因此須要掛起線程,這個方法就是負責掛起線程的
// 這裏用了LockSupport.park(this)來掛起線程,而後就停在這裏了,等待被喚醒=======
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
仔細看這個方法acquireQueued方法,是無限循環,感受若是p == head && tryAcquire(arg)條件不知足循環將永遠沒法結束,在這裏,固然不會出現死循環。由於parkAndCheckInterrupt會把當前線程掛起,從而阻塞住線程的調用棧。分析到這裏,咱們的thread2線程已經被中斷了,這個線程不會再繼續執行下去了。
3)假設如今咱們的thread1尚未釋放鎖,而如今又來了一個線程thread3。
thread3首先調用lock方法獲取鎖,首先去搶佔鎖,由於咱們知道thread1尚未釋放鎖,這個時候thread3確定搶佔失敗,因而又調用了acquire方法,接着又失敗。接着會去調用addWaiter方法,將當前線程thread3封裝成node加入到線程阻塞隊列的尾部。如今的結構以下:
而後,調用addWaiter方法後,第一步,將當前將要去獲取鎖的線程也就是thread3和獨佔模式封裝爲一個node對象。而且咱們也知道在當前的執行環境下,線程阻塞隊列不是空的,由於thread2獲取了鎖,thread2已經加入了隊列。很明顯,這個時候隊列的尾部tail節點也不是null,那麼將直接進入到if分支。將尾部tail節點賦值給咱們傳入的node節點的前驅節點。以下:
第二步:經過CAS將咱們傳遞進來的node節點設置成tail節點,而且將新tail節點設置成老tail節點的後繼節點。
在執行addWaiter方法,將thread3插入到阻塞隊列尾部後,而後繼續調用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 {
if (failed)
cancelAcquire(node);
}
}
第一次循環:獲取thread3節點的前驅節點,判斷是不是head節點,如今阻塞隊列的結果以下:
因爲thread3的節點的前驅節點是thread2,不是head,因此會直接調用shouldParkAfterFailedAcquire方法:
1 判斷thread3節點的前驅節點的waitStatus,狀態爲-1,直接返回true;
2 若是thread3節點的前驅節點的waitStatus大於0,說明這個節點被CANCEL了,一直循環向前查找,直到找到waitStatus<=0;
3 若是都不是以上的狀況,就經過CAS操做將這個前驅節點設置成-1(SIGHNAL)。
此時的結構以下,主要是thread2節點的waitStatus由0變成了-1。
第二次循環:獲取thread3節點的前驅節點,判斷是不是head節點,因爲明顯不是head節點,那麼直接進入調用shouldParkAfterFailedAcquire方法,此時,thread3節點的前驅節點的waitStatus爲-1,由於返回ture,因此接下來會調用parkAndCheckInterrupt方法,直接將當前線程thread3線程中斷。如今thread2和thread3線程都被中斷了。
其實,公平鎖和非公平鎖,不一樣點只存在兩個地方:
1 對於非公平鎖,獲取鎖的第一步就是經過CAS設置state的狀態,若是成功,則直接獲取了鎖;
2 對於公平是和非公平鎖,都會調用tryAcquire方法來獲取鎖,可是兩者是有區別的:
//非公平鎖
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
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;
}
//公平鎖
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
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;
}
}
如今thread1要開始釋放鎖了。調用unlock方法,unlock方法又調用了內部的release方法:
/**
* Releases in exclusive mode. Implemented by unblocking one or
* more threads if {@link #tryRelease} returns true.
* This method can be used to implement method {@link Lock#unlock}.
*
* @param arg the release argument. This value is conveyed to
* {@link #tryRelease} but is otherwise uninterpreted and
* can represent anything you like.
* @return the value returned from {@link #tryRelease}
*/
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
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;
// 其實就是重入的問題,若是c==0,也就是說沒有嵌套鎖了,能夠釋放了,不然還不能釋放掉
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
調用tryRelease方法釋放鎖,獲取當前AQS的state,並減去1,判斷當前線程是否等於AQS的exclusiveOwnerThread,若是不是,就拋異常,這就保證了加鎖和釋放鎖必須是同一個線程。若是(state-1)的結果不爲0,說明鎖被重入了,須要屢次unlock。若是(state-1)等於0,咱們就將AQS的ExclusiveOwnerThread設置爲null。若是上述操做成功了,也就是tryRelase方法返回了true,那麼就會判斷當前隊列中的head節點,當前結構以下:
若是head節點不爲null,而且head節點的waitStatus不爲0,咱們就調用unparkSuccessor方法去喚醒head節點的後繼節點。
/**
* Wakes up node's successor, if one exists.
*
* @param node the node
*/
// 喚醒後繼節點
// 從上面調用處知道,參數node是head頭結點
private void unparkSuccessor(Node node) {
/*
* If status is negative (i.e., possibly needing signal) try
* to clear in anticipation of signalling. It is OK if this
* fails or if status is changed by waiting thread.
*/
int ws = node.waitStatus;
// 若是head節點當前waitStatus<0, 將其修改成0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
/*
* Thread to unpark is held in successor, which is normally
* just the next node. But if cancelled or apparently null,
* traverse backwards from tail to find the actual
* non-cancelled successor.
*/
// 下面的代碼就是喚醒後繼節點,可是有可能後繼節點取消了等待(waitStatus==1)
// 從隊尾往前找,找到waitStatus<=0的全部節點中排在最前面的
Node s = node.next;
if (s == null || s.waitStatus > 0) {
s = null;
// 從後往前找,仔細看代碼,沒必要擔憂中間有節點取消(waitStatus==1)的狀況
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
if (s != null)
// 喚醒線程
LockSupport.unpark(s.thread);
}
第一步:獲取head節點的waitStatus,若是小於0,就經過CAS操做將head節點的waitStatus修改成0,如今是:
第二步:尋找head節點的下一個節點,若是這個節點的waitStatus小於0,就喚醒這個節點,不然遍歷下去,找到第一個waitStatus<=0的節點,並喚醒。如今thread2線程被喚醒了,咱們知道剛纔thread2在acquireQueued被中斷,如今繼續執行,又進入了for循環,當前節點的前驅節點是head而且調用tryAquire方法得到鎖而且成功。那麼設置當前Node爲head節點,將裏面的thead和prev設置爲null。 此時,thread2獲取了鎖,併成爲頭節點,原來的頭節點釋放掉,等待被回收。