在前面的文章中介紹了獨(dú)占式同步狀態(tài)的獲取和釋放以及共享式同步狀態(tài)的獲取和釋放,在前面的文章中并沒有介紹線程的阻塞和喚醒,在這篇文章中LZ將介紹在AQS中線程的阻塞和喚醒。
在線程獲取同步狀態(tài)失敗后,會(huì)加入到CHL隊(duì)列中去,并且該節(jié)點(diǎn)會(huì)自旋式的不斷的獲取同步狀態(tài),在獲取同步狀態(tài)的過程中,需要判斷當(dāng)前線程是否需要被阻塞。其主要方法在acquireQueued(final Node node, int arg)方法的定義里面:
if (shouldParkAfterFailedAcquire(p, node) && parkAndCheckInterrupt())
interrupted = true;
通過這段代碼可以看出,線程在獲取同步狀態(tài)失敗后,并不是立馬進(jìn)入等待狀態(tài),而是需要判斷當(dāng)前線程是否需要被阻塞。檢查是否需要阻塞的方法shouldParkAfterFailedAcquire(p, node),其定義如下:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
// 獲取前驅(qū)節(jié)點(diǎn)的等待狀態(tài)
int ws = pred.waitStatus;
// 若果等待狀態(tài)的值為SIGNAL,則返回true 表示當(dāng)前線程需要等待
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
/*
* 前驅(qū)節(jié)點(diǎn)的狀態(tài)>0,為CANCLE狀態(tài),表示該節(jié)點(diǎn)被中斷或者超時(shí),需要
* 從CHL中移除。
*/
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
/* 前驅(qū)節(jié)點(diǎn)為 PROPAGATE或者CONDITION 將前驅(qū)節(jié)點(diǎn)的等待狀態(tài)以CAS的方式
* 更新為SIGNAL
*/
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
上面這段代碼主要的功能就是判斷當(dāng)前線程是否需要阻塞,當(dāng)該方法的返回值為true時(shí),表示當(dāng)前線程需要等待,反之返回false.其規(guī)則如下:
- 如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的等待狀態(tài)為SIGNAL,則返回true
- 如果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的等待狀態(tài)為CALCLE,則表示該線程的前驅(qū)節(jié)點(diǎn)已經(jīng)被中斷或者超時(shí),需要從CHL中刪除,直到回溯到ws <= 0,返回false
- 若果當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的等待狀態(tài)為非SIGNAL,非CANCLE,則以CAS的方式設(shè)置其前驅(qū)節(jié)點(diǎn)為的狀態(tài)為SIGNAL,返回false.
當(dāng) shouldParkAfterFailedAcquire(Node pred, Node node)方法返回true時(shí),會(huì)執(zhí)行 parkAndCheckInterrupt()方法。該方法定義如下:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
該方法就實(shí)現(xiàn)了將線程掛起,從而阻塞住線程的調(diào)用棧,已達(dá)到阻塞線程的目的。其內(nèi)部則是調(diào)用了LockSupport工具類的park()方法來實(shí)現(xiàn)的。
當(dāng)同步狀態(tài)被釋放后,需要喚醒后繼節(jié)點(diǎn):
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
// 喚醒后繼節(jié)點(diǎn)
unparkSuccessor(h);
return true;
}
return false;
}
unparkSuccessor()方法的定義如下:
private void unparkSuccessor(Node node) {
// 當(dāng)前節(jié)點(diǎn)的等待狀態(tài)
int ws = node.waitStatus;
// 當(dāng)前節(jié)點(diǎn)狀態(tài) < 0,則CAS方法設(shè)置當(dāng)前狀態(tài)為0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
// 當(dāng)前節(jié)點(diǎn)的后繼節(jié)點(diǎn)
Node s = node.next;
// 如果后繼節(jié)點(diǎn)為空或者后繼節(jié)點(diǎn)的等待狀態(tài)為CANCLE
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;
}
// 喚醒后繼節(jié)點(diǎn)
if (s != null)
LockSupport.unpark(s.thread);
}
在這里很多人大概會(huì)有疑問為什么是從尾部回溯找到一個(gè)可用的節(jié)點(diǎn),我們不妨先來回顧下添加節(jié)點(diǎn)的方法,
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
在上面代碼中我們可以看到,將節(jié)點(diǎn)添加到尾部是一個(gè)CAS操作,但是t.next = node 這個(gè)操作不是線程安全的,如果一個(gè)線程在執(zhí)行CAS添加尾部之后正好有線程釋放了同步狀態(tài),這個(gè)時(shí)候如果是從head到tail的遍歷,則會(huì)出現(xiàn)中間斷裂的情況,而從尾部回溯是一定可以遍歷到所有節(jié)點(diǎn)的。
上面線程的喚醒和等待都是通過LockSupport工具類中的方法來實(shí)現(xiàn)的,我們來看看LockSupport這個(gè)工具類的。
LockSupport
1.LockSupport介紹
LockSupport是用于創(chuàng)建鎖和其他同步類的基本線程阻塞原語。
LockSupport定義了一組以park開頭的方法用來阻塞線程,以及以u(píng)npark(Thread thread)方法來喚醒一個(gè)線程。park方法和unpark方法提供了阻止和解除阻塞線程的有效手段,該方法不會(huì)遇到Threaad.suspend和Thread.resum方法導(dǎo)致的死鎖問題。
2.LockSupport方法列表
- getBlocker(Thread t) :返回提供給最近調(diào)用尚未解除阻塞的park 方法調(diào)用的 blocker 對(duì)象,如果調(diào)用不阻止,則返回null
- park() : 禁止當(dāng)前線程進(jìn)行線程調(diào)度,除非許可證可用。
- park(Object blocker) :禁止當(dāng)前線程進(jìn)行線程調(diào)度,除非許可證可用。
- parkNanos(long nanos) :禁止當(dāng)前線程進(jìn)行線程調(diào)度,直到指定的等待時(shí)間,除非許可證可用。
- parkNanos(Object blocker, long nanos) :禁止當(dāng)前線程進(jìn)行線程調(diào)度,直到指定的等待時(shí)間,除非許可證可用。
- parkUntil(long deadline) :禁止當(dāng)前線程進(jìn)行線程調(diào)度,直到指定的截止時(shí)間,除非許可證可用。
- parkUntil(Object blocker, long deadline) :禁止當(dāng)前線程進(jìn)行線程調(diào)度,直到指定的截止時(shí)間,除非許可證可用。
- unpark(Thread thread) :為給定的線程提供許可證(如果尚未提供)。
上述方法中參數(shù) blocker 是用來標(biāo)識(shí)當(dāng)前線程在等待的對(duì)象,改對(duì)象主要用于問題的排查和系統(tǒng)給的監(jiān)控。
接下來我們?cè)诳纯磒ark和unpark方法的定義:
park:
public static void park() {
UNSAFE.park(false, 0 L);
}
unpark:
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
從上面方法的定義中我們可以看出park和unpark方法都是通過UNSAFE類中的park和unpark方法來實(shí)現(xiàn)的。其UNSAFE中park和unpark的方法定義如下:
public native void unpark(Object var1);
public native void park(boolean var1, long var2);
可以看出這2個(gè)方法都是本地方法。Unsafe是一個(gè)不安全的類,主要用于執(zhí)行低級(jí)別、不安全的方法集合。盡管Unsafe類中的方法都是public的,但是我們還是不能在自己的java代碼中調(diào)用這個(gè)類中的方法,因?yàn)橹挥惺谛诺拇a才能獲取到該類的實(shí)例。
掃碼關(guān)注公眾號(hào),回復(fù)
1024獲取最新大廠面試資料
