在java.util.concurrent.locks包中有很多Lock的實(shí)現(xiàn)類年枕,常用的有ReentrantLock倍谜、ReadWriteLock(實(shí)現(xiàn)類ReentrantReadWriteLock),內(nèi)部實(shí)現(xiàn)都依賴AbstractQueuedSynchronizer類。
隊(duì)列同步器AQS是用來(lái)構(gòu)建鎖或其他同步組件的基礎(chǔ)框架,內(nèi)部使用一個(gè)int成員變量表示同步狀態(tài),通過(guò)內(nèi)置的FIFO隊(duì)列來(lái)完成資源獲取線程的排隊(duì)工作亚情,其中內(nèi)部狀態(tài)state,等待隊(duì)列的頭節(jié)點(diǎn)head和尾節(jié)點(diǎn)head哈雏,都是通過(guò)volatile修飾楞件,保證了多線程之間的可見(jiàn)。
public abstract class AbstractQueuedSynchronizer extends
AbstractOwnableSynchronizer implements java.io.Serializable {
//等待隊(duì)列的頭節(jié)點(diǎn)
private transient volatile Node head;
//等待隊(duì)列的尾節(jié)點(diǎn)
private transient volatile Node tail;
//同步狀態(tài)
private volatile int state;
protected final int getState() { return state;}
protected final void setState(int newState) { state = newState;}
...
}
AQS內(nèi)部有一個(gè)FIFO的同步隊(duì)列裳瘪,用于完成同步狀態(tài)的管理土浸。
static final class Node {
static final Node SHARED = new Node();
static final Node EXCLUSIVE = null;
static final int CANCELLED = 1;
static final int SIGNAL = -1;
static final int CONDITION = -2;
static final int PROPAGATE = -3;
volatile int waitStatus;
volatile Node prev;
volatile Node next;
volatile Thread thread;
Node nextWaiter;
...
}
頭結(jié)點(diǎn)代表當(dāng)前持有鎖的線程,每當(dāng)有線程競(jìng)爭(zhēng)失敗彭羹,就會(huì)插入到隊(duì)列的尾部黄伊,tail節(jié)點(diǎn)始終指向隊(duì)列中的最后一個(gè)元素。
每個(gè)節(jié)點(diǎn)中派殷, 除了存儲(chǔ)了當(dāng)前線程还最,前后節(jié)點(diǎn)的引用以外,還有一個(gè)waitStatus變量毡惜,用于描述節(jié)點(diǎn)當(dāng)前的狀態(tài)拓轻。多線程并發(fā)執(zhí)行時(shí),隊(duì)列中會(huì)有多個(gè)節(jié)點(diǎn)存在经伙,這個(gè)waitStatus其實(shí)代表對(duì)應(yīng)線程的狀態(tài):有的線程可能獲取鎖因?yàn)槟承┰蚍艞壐?jìng)爭(zhēng)扶叉;有的線程在等待滿足條件,滿足之后才能執(zhí)行等等橱乱。一共有4種狀態(tài):
- CANCELLED 取消狀態(tài)
- SIGNAL 等待觸發(fā)狀態(tài)
- CONDITION 等待條件狀態(tài)
- PROPAGATE 狀態(tài)需要向后傳播
1 獨(dú)占模式獲取鎖操作
在將Node節(jié)點(diǎn)插入到尾部之后,并不是立即掛起該節(jié)點(diǎn)中線程粱甫,因?yàn)樵诓迦胨倪^(guò)程中泳叠,前面的線程可能已經(jīng)執(zhí)行完成,所以它會(huì)先進(jìn)行自旋操作acquireQueued(node, arg)茶宵,嘗試讓該線程重新獲取鎖危纫!當(dāng)條件滿足獲取到了鎖則可以從自旋過(guò)程中退出,否則繼續(xù)。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
//如果它的前繼節(jié)點(diǎn)為頭結(jié)點(diǎn),嘗試獲取鎖,獲取成功則返回
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);
}
}
如果沒(méi)獲取到鎖种蝶,則判斷是否應(yīng)該掛起契耿,而這個(gè)判斷通過(guò)它的前驅(qū)節(jié)點(diǎn)的waitStatus來(lái)確定。
rivate static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
return true;
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
如果前驅(qū)節(jié)點(diǎn)的waitStatus為:
- SIGNAL螃征,則返回true表示應(yīng)該掛起當(dāng)前線程搪桂,掛起該線程,并等待被喚醒,被喚醒后進(jìn)行中斷檢測(cè),如果發(fā)現(xiàn)當(dāng)前線程被中斷盯滚,那么拋出InterruptedException并退出循環(huán)
- 大于0踢械,將前驅(qū)節(jié)點(diǎn)踢出隊(duì)列,返回false
- 小于0魄藕,也是返回false内列,不過(guò)先將前驅(qū)節(jié)點(diǎn)waitStatus設(shè)置為SIGNAL,使得下次判斷時(shí),將當(dāng)前節(jié)點(diǎn)掛起
獲取獨(dú)占鎖流程總結(jié)如下:
AQS的模板方法acquire通過(guò)調(diào)用子類自定義實(shí)現(xiàn)的tryAcquire獲取同步狀態(tài)失敗后->將線程構(gòu)造成Node節(jié)點(diǎn)(addWaiter)->將Node節(jié)點(diǎn)添加到同步隊(duì)列對(duì)尾(addWaiter)->節(jié)點(diǎn)以自旋的方法獲取同步狀態(tài)(acquirQueued)背率。在節(jié)點(diǎn)自旋獲取同步狀態(tài)時(shí)话瞧,只有其前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)的時(shí)候才會(huì)嘗試獲取同步狀態(tài),如果該節(jié)點(diǎn)的前驅(qū)不是頭節(jié)點(diǎn)或者該節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)但獲取同步狀態(tài)失敗寝姿,則判斷當(dāng)前線程是否需要阻塞交排,如果需要阻塞則在被喚醒過(guò)后才返回。
2 獨(dú)占模式釋放鎖操作
既然是釋放,那肯定是持有鎖的該線程執(zhí)行釋放操作会油,即head節(jié)點(diǎn)中的線程釋放鎖个粱。
AQS中的release釋放同步狀態(tài)和acquire獲取同步狀態(tài)一樣,都是模板方法翻翩,tryRelease釋放的具體操作都有子類去實(shí)現(xiàn)都许,父類AQS只提供一個(gè)算法模板。
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
/**如果node的后繼節(jié)點(diǎn)不為空且不是作廢狀態(tài),則喚醒這個(gè)后繼節(jié)點(diǎn),否則從末尾開(kāi)始尋找合適的節(jié)點(diǎn),如果找到,則喚醒*/
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
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);
}
首先調(diào)用子類的tryRelease()方法釋放鎖嫂冻,然后喚醒后繼節(jié)點(diǎn)胶征,在喚醒的過(guò)程中,需要判斷后繼節(jié)點(diǎn)是否滿足情況桨仿,如果后繼節(jié)點(diǎn)不是作廢狀態(tài)睛低,則喚醒這個(gè)后繼節(jié)點(diǎn),否則從tail節(jié)點(diǎn)向前尋找合適的節(jié)點(diǎn)服傍,如果找到钱雷,則喚醒之。
參考資料: