前題
在閱讀本文之前,建議先閱讀我的《初步瞭解AQS是什麼(一)》,畢竟有一些內容是和前文是相通的,如果對AQS熟悉的話,也可以直接往下看,不過還是先建議先看下
公平鎖和非公平鎖
ReentrantLock預設使用的是非公平鎖,除非在構造方法裡面傳入true就可以變為公平鎖啦!
public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
複製程式碼
先看看公平鎖的lock
final void lock() {
acquire(1);
}
//----------------------------acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//-----------------------------tryAcquire
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;
}
複製程式碼
非公平鎖的lock
final void lock() {
if (compareAndSetState(0, 1)) // 非公平鎖會先試著去佔有這個鎖先,如果不行就再獲取!
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//-------------------------acquire
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
//-----------------------tryAcquire
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
//-----------------------nonfairTryAcquire
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;
}
複製程式碼
總結:
-
非公平鎖在lock的時候,在tryAcquire函式裡面會CAS獲取state,如果恰好成功,那麼就直接取得鎖啦,不用進行下面的操作了
-
非公平鎖在 CAS 失敗後,和公平鎖一樣都會進入到 tryAcquire 方法,在 tryAcquire 方法中,如果發現鎖這個時候被釋放了(state == 0),非公平鎖會直接 CAS 搶鎖,但是公平鎖會判斷等待佇列是否有執行緒處於等待狀態,如果有則不去搶鎖,乖乖排到後面。
-
非公平鎖如果兩次CAS都不成功,那麼接下來的操作和公平鎖一樣啦!都要進入到阻塞佇列等待喚醒。
-
非公平鎖會有更好的效能,因為它的吞吐量比較大。當然,非公平鎖讓獲取鎖的時間變得更加不確定,可能會導致在阻塞佇列中的執行緒長期處於飢餓狀態。
生產者消費者模式
在讀下面的內容的時候,讓我們來先了解一下生產者和消費者的模式,主要有個例子,更好地理解下面的內容!
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class BoundedBuffer {
final Lock lock = new ReentrantLock();
// condition 依賴於 lock 來產生
final Condition notFull = lock.newCondition();
final Condition notEmpty = lock.newCondition();
final Object[] items = new Object[100];
int putptr, takeptr, count;
// 生產
public void put(Object x) throws InterruptedException {
lock.lock();
try {
while (count == items.length)
notFull.await(); // 佇列已滿,等待,直到 not full 才能繼續生產
items[putptr] = x;
if (++putptr == items.length) putptr = 0;
++count;
notEmpty.signal(); // 生產成功,佇列已經 not empty 了,發個通知出去
} finally {
lock.unlock();
}
}
// 消費
public Object take() throws InterruptedException {
lock.lock();
try {
while (count == 0)
notEmpty.await(); // 佇列為空,等待,直到佇列 not empty,才能繼續消費
Object x = items[takeptr];
if (++takeptr == items.length) takeptr = 0;
--count;
notFull.signal(); // 被我消費掉一個,佇列 not full 了,發個通知出去
return x;
} finally {
lock.unlock();
}
}
}
複製程式碼
上面只是貼個程式碼,先讓讀者知道怎麼用ReentrantLock和Condition,關於更多的細節,請讀者自行百度即可。
ConditionObject
Condition介面的實現類。Condition的方法依賴於ReentrantLock,而ReentrantLock又依賴於AbstractQueueSynchronized類。
內部結構
private static final long serialVersionUID = 1173984872572414699L;
/** First node of condition queue. */
private transient Node firstWaiter;
/** Last node of condition queue. */
private transient Node lastWaiter;
複製程式碼
條件佇列
在AQS中,其中執行緒等待的佇列我們成為阻塞佇列,這裡先引入條件佇列(condition queue),這裡先上圖看兩者的關係。
區別
- 阻塞佇列和條件佇列都是node節點,也就是Node的例項。因為條件佇列的節點是需要轉移到阻塞佇列的。
- ReentrantLock是可以建立多個Condition例項的,則這裡會有condition1和condition2,並且ConditionObject中只有兩個和節點有關的屬性 firstWaiter和nextWaiter
- 每個Condition都有一個條件佇列與之關聯。當呼叫await()方法的時候,當先執行緒則會阻塞在當前地方,不會往下執行,然後將當前執行緒封裝成Node節點,新增到條件佇列的隊尾。
- 呼叫Condition.signal()會觸發喚醒。但是喚醒的是隊頭節點。也就是將對應執行緒的條件佇列的對頭(firstWaiter指向的節點)移到阻塞佇列的隊尾,等待獲取鎖。獲取鎖之後,await才會返回,然後繼續往下執行。
原始碼分析
await:讓執行緒掛起等待,並交出鎖
await():是可以被中斷的,呼叫這個方法的執行緒會阻塞,直到呼叫Signal()或者被中斷。
awaitUninterruptibly():不可被中斷,就是說有中斷訊號來了但不會響應。
public final void await() throws InterruptedException {
//剛開始的時候看看有沒有被中斷過,如果有就響應唄。
if (Thread.interrupted())
throw new InterruptedException();
// 把當前執行緒封裝為Node節點,新增到條件佇列的隊尾。
Node node = addConditionWaiter();
//因為在await之前,這個執行緒肯定是擁有鎖的,所以這裡完全釋放鎖,為什麼要完全釋放鎖,是因為考慮到重入的問題。
//這裡的saveState返回的是釋放鎖之前的state值。
int savedState = fullyRelease(node);
int interruptMode = 0;
//這裡判斷的await的節點有沒有進入到阻塞佇列,也叫同步佇列。
//如果進入,返回true,不會執行while迴圈
//如果沒進入,就等著唄,等待其他執行緒叫醒你或者中斷訊號來臨唄。
while (!isOnSyncQueue(node)) {
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
//到了這一步,已經說明node節點在阻塞佇列了。那麼就爭搶資源,也就是AQS的爭搶資源的方式。
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
if (node.nextWaiter != null) // clean up if cancelled
unlinkCancelledWaiters();
if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);
}
複製程式碼
addConditionWaiter:將條件佇列的節點加入阻塞佇列
private Node addConditionWaiter() {
Node t = lastWaiter; //指向條件佇列的尾節點
// If lastWaiter is cancelled, clean out.
if (t != null && t.waitStatus != Node.CONDITION) {
//如果進入到這裡,就說明條件佇列這時候不為空,但是尾節點取消了
//因為在尾節點加入的時候,會把ws設施為CONDITION的。
//先把取消等待的節點清除,然後再找到真正的尾節點
unlinkCancelledWaiters();
t = lastWaiter;
}
//之前說過,加入條件佇列的時候會把ws設定為CONDITION
Node node = new Node(Thread.currentThread(), Node.CONDITION);
if (t == null)//這裡說明條件佇列為空,也就是說第一次加入
//設定好頭指標
firstWaiter = node;
else
//那麼這裡就是條件佇列不為空唄,那就加入真正尾節點後頭就好了
t.nextWaiter = node;
lastWaiter = node;
return node;
}
複製程式碼
在addConditionWaiter中,有一個unlinkCancelledWaiters()方法,顧名思義就是取消不等待的節點,因為條件佇列是單向連結串列,所以涉及到連結串列的操作。
就是說如果在await的時候,節點取消等待或者是說節點入隊的時候,發現最後一個節點已經取消了,那麼就呼叫一次這個方法
// 等待佇列是一個單向連結串列,遍歷連結串列將已經取消等待的節點清除出去
// 純屬連結串列操作,很好理解,看不懂多看幾遍就可以了
private void unlinkCancelledWaiters() {
Node t = firstWaiter;
Node trail = null;
while (t != null) {
Node next = t.nextWaiter;
// 如果節點的狀態不是 Node.CONDITION 的話,這個節點就是被取消的
if (t.waitStatus != Node.CONDITION) {
t.nextWaiter = null;
if (trail == null)
firstWaiter = next;
else
trail.nextWaiter = next;
if (next == null)
lastWaiter = trail;
}
else
trail = t;
t = next;
}
}
複製程式碼
fullyRelease: 完全釋放獨佔鎖
在await方法中,把節點加入到條件佇列之後,就需要完全釋放鎖了。因為考慮到重入的問題,所以必須要完全釋放。
例子:condition.await之前,當前節點就已經執行了兩次lock()操作,那麼state為2,也就是說擁有兩把鎖。那麼fullyRelease就應該設定state為0,返回2,返回沒釋放鎖之前所擁有的鎖的數量。然後再進行掛起。當被喚醒的時候,依舊需要兩把鎖才能進行執行下去。
final int fullyRelease(Node node) {
boolean failed = true;
try {
int savedState = getState();
if (release(savedState)) {
failed = false;
return savedState; //如果完全釋放鎖成功,那麼這裡返回的是釋放鎖之前所用有鎖的個數
} else {
//到這一步說明釋放鎖不成功,比如說有個沒有鎖的執行緒但是執行了釋放鎖,那麼肯定會到達這一步,所以會跑出異常
throw new IllegalMonitorStateException();
}
} finally {
if (failed)
//到達這裡說明faile = true,也就是說release(savedState)返回的是false,丟擲了異常,說明該節點操作不當,那麼就得設定為取消,方便後面有節點加入的時候,觸發unlinkCancelledWaiters,把這個節點請出去
node.waitStatus = Node.CANCELLED;
}
}
複製程式碼
isOnSyncQueue():判斷是否進入阻塞佇列
在await方法中,完全釋放鎖之後,這個方法會判斷該執行緒代表的佇列是否在阻塞佇列中,注意是阻塞佇列。
前面我們也說過,在節點加入阻塞佇列的時候,會把ws 設定為Node.Condition
final boolean isOnSyncQueue(Node node) {
//如果在條件佇列的節點被移到阻塞佇列,那麼waitStatus會被設定為0,也就是在signal的時候,這個後面說
//所以,如果當前節點的waitStatus如果還是Condition的話,那麼肯定不在阻塞佇列中
//因為是把條件佇列的firstWaiter移動到阻塞佇列的,所以firstWaiter的pre是null,那麼肯定不在阻塞佇列中。
if (node.waitStatus == Node.CONDITION || node.prev == null)
return false;
if (node.next != null) // If has successor, it must be on queue
//這裡很簡單理解啦,在上面的前提下,如果next不為null,那麼肯定在阻塞佇列中啦,因為條件佇列用的是nextWaiter,阻塞佇列用的是next屬性
return true;
//上面曾經試過用node.pre == null 判斷不在阻塞佇列中,那麼node.pre != null就可以判斷在阻塞佇列中了嗎?
//答案非也,因為在節點加入AQS的阻塞佇列的時候,會先將節點的pre設定為tail,然後再進行CAS操作將當前節點設定為tail節點,如果這個CAS操作失敗了,那麼此時tail節點並不是當前節點,那麼也說明該節點並不在佇列中
//所以這裡有一個從尾節點往前找的函式
return findNodeFromTail(node);
}
//這個函式很簡單,就是如果沒在佇列的話,那麼t的最終結果為null,返回false,如果找到就返回true唄
//這裡的不在佇列有種情況,就是上面說的CAS將當前節點設定為tail節點失敗,讀者可以整理下思路。
private boolean findNodeFromTail(Node node)
Node t = tail;
for (;;) {
if (t == node)
return true;
if (t == null)
return false;
t = t.prev;
}
}
複製程式碼
如果isOnSynchronized返回false的話,那麼就先掛起等待唄,也就是等待其他執行緒signal或者中斷 喚醒執行緒,將這個執行緒加入阻塞佇列!
signal:喚醒執行緒,讓執行緒得回鎖
前面說到執行緒掛起了,就等待著被喚醒唄!那麼這裡先說下signal函式,方便後面理解。
喚醒操作一般是由另外一個執行緒操作的,呼叫同一個物件的signal物件喚醒就好,其實喚醒的操作就是將節點從條件佇列移動到阻塞佇列中。
public final void signal() {
if (!isHeldExclusively())//如果執行緒沒有擁有排他鎖,也就是沒有獨佔鎖,丟擲異常唄
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
//到這裡就是找到條佇列的第一個節點,然後從這個節點找並沒有取消等待的執行緒!因為可能這時候firstWaiter這個節點已經取消等待了唄。
doSignal(first);
}
//----------------------------------------doSignal--------------------------------------
//從隊友找到第一個加入阻塞佇列的節點
//因為之前說過條件佇列的節點取消放棄等待
private void doSignal(Node first) {
do {
//將firstWaiter指向first後面一個節點,因為現在這個firstWaiter將要離開佇列啦!
if ( (firstWaiter = first.nextWaiter) == null)
//到這裡說明如果firstWaiter離開之後條件佇列都為空了,那麼也lastWaiter指標也置為null啦
lastWaiter = null;
//斷開firstWaiter啦!
first.nextWaiter = null;
} while (!transferForSignal(first) && //這個while迴圈就是判斷這個first指向的節點是否轉移成功,如果返回false的話,那麼就是轉移不成功,也就是說取消了,那麼就查詢下一個節點。返回true就是說加入阻塞佇列成功啦,那麼這個函式就完成使命啦!
(first = firstWaiter) != null);
}
//-----------------------------------------------transferForSignal---------------------
final boolean transferForSignal(Node node) {
//這裡之前說過加入條件佇列的時候節點的waitStatus會初始化為Condition,如果這裡CAS更新失敗,說明狀態不為Condition,就說明取消了唄。那麼返回false,繼續尋找下一個想加入阻塞佇列的節點。
//如果更新成功就把節點的ws設定為0,為初始態,就是為了加入阻塞佇列。
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
//到達這裡說明CAS成功,ws也為0,那麼說明就可以自旋進入阻塞佇列啦!
Node p = enq(node);//返回加入阻塞佇列後的前驅節點。
int ws = p.waitStatus;
//這裡是根據前驅節點的狀態,如果ws>0,說明就是取消等待啦
// 如果ws < 0,就是說該節點沒有取消等待進入阻塞佇列。就先把加入阻塞佇列的節點的ws設定為SIGNAL,這個就不多說啦,具體看上一篇文章即可!
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
//到這裡說明節點要麼取消,要麼CAS想要加入阻塞佇列的節點的狀態失敗,那麼就先喚醒,為啥要喚醒呢?後面講!
LockSupport.unpark(node.thread);
// 不管有沒有執行上一個if語句,都說明進入阻塞佇列已經成功啦!
return true;
}
複製程式碼
檢查中斷狀態
在signal之後,執行緒所代表的node節點肯定已經加入到阻塞佇列中啦!然後就準備去獲取鎖。
之前不是說到執行緒已經掛起了嗎?signal之後或者被中斷就已經喚醒啦
int interruptMode = 0;
while (!isOnSyncQueue(node)) {
// 執行緒掛起
LockSupport.park(this);
if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break;
}
複製程式碼
有以下幾種情況會讓執行緒從LockSupport.park(this);這步往下走
- signal之後進入阻塞佇列,等待前驅節點釋放鎖,釋放鎖的時候就會呼叫 LockSupport.unPark(node.thread),也就是被喚醒啦!
- 當執行緒在park的時候,有其他執行緒對它進行了中斷。
- 在signal的時候,進行過進隊操作,但是前驅節點已經取消等待了或者CAS前驅節點的狀態為SIGNAL也失敗
- 假喚醒。這個也是存在的,和 Object.wait() 類似,都有這個問題
執行緒被喚醒之後,第一步就是執行checkInterruptWhileWaiting(node)這個函式啦,如果返回非0,就是說await的執行緒在park期間或者說signal之後(unpark之後)被中斷過,所以就要區分到底是signal之前中斷還是signal之後中斷。
如果返回0,那麼就是說執行緒在park的時候,沒有被中斷,被unpark就是在阻塞佇列正常被喚醒的!
checkInterruptWhileWaiting返回的值
- 如果在signal之前已經進行中斷,那麼返回THROW_IE -1
- 如果在signal之後已經進行中斷,那麼返回REINTERRUPT 1
- 如果返回0,那麼表示在park期間沒有進行中斷
private int checkInterruptWhileWaiting(Node node) {
return Thread.interrupted() ?
(transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
0;
}
複製程式碼
如何區分signal之前中斷還是之後中斷
主要在transferAfterCancelledWait這個方法裡,我們下面來看一下它的程式碼
final boolean transferAfterCancelledWait(Node node) {
if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
//如果進行到這裡,那麼說明是signal之前就被中斷的。因為signal的時候,會把await在條件佇列的執行緒的waitStatus置為0,if判斷裡面的那個CAS操作就會失敗了。所以就說明如果到這裡,那麼肯定還在條件對了,並且沒有被signal
enq(node);//從條件佇列進入阻塞佇列,注意這裡的nextWaiter並沒有置位空,如果後面還有節點,那麼也會一起帶去阻塞佇列
return true;
}
/*
* If we lost out to a signal(), then we can't proceed
* until it finishes its enq(). Cancelling during an
* incomplete transfer is both rare and transient, so just
* spin.
*/
//到達這裡說明CAS失敗,有兩種情況
// 1. 是已經signal完成,已經把節點移到阻塞佇列中了,就不會進入下面那個while迴圈
// 2. 還沒有完全轉移到阻塞佇列中,那麼就進入while循壞咯,自旋等待,直到進入阻塞佇列為止,但是這種情況如翻譯所示,是非常罕見和稀少的
while (!isOnSyncQueue(node))
Thread.yield();
return false;
複製程式碼
所以經過上面的分析,只要是某個執行緒被中斷,那麼不管這個執行緒所代表的node節點是不是firstWaiter,也就是是不是會被signal,都會被喚醒,然後進入阻塞佇列。
獲取獨佔鎖
我們回到await函式,在上面的while退出之後,也就是我們的節點不管是被signal也好還是被中斷也好,都已經進入到阻塞佇列了,這點是毋庸置疑的。進入阻塞佇列後幹嘛呢?當然是為了獲取鎖啦,那麼如何獲取鎖呢?還記得上一節我們講的進入阻塞佇列之後如何獲取鎖麼?如果不記得就去看看吧!這裡放出連線
// acquireQueued上節已經分析過,不再綴訴。這裡判斷中斷是不是signal之後中斷的
//如果是signal前的,到後面就直接丟擲異常,不會進行這一步。
//如果是signal之後,但是在阻塞佇列等待的時候被中斷了,也就是說acquireQueued的時候被中斷過。前面退出while迴圈的時候可能是沒有中斷退出(interruptMode == 0),也可能是中斷退出,只要acquireQueued的時候被中斷過,並且是signal之後,都要重新設定下interruptMode,這裡讀者可以好好捋一下!
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT;
複製程式碼
if (node.nextWaiter != null) // clean up if cancelled
//如果執行到這裡,那麼就是說是這個執行緒時被中斷加入阻塞佇列的,不是通過signal加入的,因為signal的話的nextWaiter早就已經被設定為null啦!
unlinkCancelledWaiters();
複製程式碼
if (interruptMode != 0) // 如果到這裡肯定是被中斷啦,管你是signal之前還是之後
reportInterruptAfterWait(interruptMode);
//-----------------reportInterruptAfterWait------------------------------------------------------+
private void reportInterruptAfterWait(int interruptMode)
throws InterruptedException {
if (interruptMode == THROW_IE) //就是這裡,如果signal之前就被中斷,丟擲異常!!
throw new InterruptedException();
else if (interruptMode == REINTERRUPT) //如果是signal之後中斷,那麼就設定下標誌位就好啊!讓開發者自己去寫外層邏輯去檢測!
selfInterrupt();
}
//---------------------------selfInterrupt------------------------------------
static void selfInterrupt() {
Thread.currentThread().interrupt();
}
複製程式碼
AQS 獨佔鎖的取消排隊
下面我們來分析下,如何取消鎖之間的競爭,當有幾個執行緒想競爭某個鎖的時候,我們希望有一個執行緒不去競爭這個鎖了。
在第一篇文章中我們用的lock,如果有中斷是不會丟擲異常的,只是標記一下狀態而已,具體的由外層函式決定。
在ReentrantLock中,有這樣的一個lock方法,可以檢測到中斷並且丟擲異常。
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
//------------------------------------------------------------
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException(); //如果還沒開始競爭就中斷,也會丟擲異常返回
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
//-------------------------------doAcquireInterruptibly--------------------------------
private void doAcquireInterruptibly(int arg)
throws InterruptedException {
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
for (;;) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return;
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
// 就是這裡,一旦異常,馬上結束這個方法,丟擲異常。
// 這裡不再只是標記這個方法的返回值代表中斷狀態
// 而是直接丟擲異常,而且外層也不捕獲,一直往外拋到 lockInterruptibly,
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
複製程式碼
上面我們看到,acquireInterruptibly裡面的doAcquireInterruptibly在競爭鎖的時候,一旦其他執行緒對其產生了中斷,那麼這個執行緒就馬上結束,不再去競爭。如果是acquired的話,如果在競爭鎖期間其他執行緒對其產生中斷,那麼先先搶得鎖再說,中斷後面再處理!這就是他們的區別啦!
總結
- 分析ReentrantLock公平鎖和非公平鎖的原始碼,理解其中的區別。
- 引入條件佇列,並且和阻塞佇列進行比較,分析他們的關係
- 對Condition的底層程式碼進行了部分的分析,主要是Node節點的await和signal的轉換以及如何處理中斷的問題
- 對AQS消除鎖競爭的兩種方式進行了原始碼分析,理解二者的不同
通過寫這篇部落格,對AQS的原始碼有了進一步的瞭解,本文主要花了大量的篇幅去寫ConditionObject,主要是為了弄明白Node節點在條件佇列和阻塞佇列的轉換的過程,同時自己由於之前對中斷不是很熟悉,所以補了一些知識,起碼比之前熟悉了一點,也算是一點小進步吧!後面會繼續瞭解AQS,下次見!