獨占模式
1.獲取獨占鎖
1.1 acquire()
/**
* 獲取獨占鎖渠概,對中斷不敏感劣光。
* 首先嘗試獲取一次鎖航揉,如果成功熟呛,則返回临扮;
* 否則會把當前線程包裝成Node插入到隊列中诫惭,在隊列中會檢測是否為head的直接后繼窃祝,并嘗試獲取鎖,
* 如果獲取失敗,則會通過LockSupport阻塞當前線程众旗,直至被釋放鎖的線程喚醒或者被中斷竟贯,隨后再次嘗試獲取鎖,如此反復逝钥。
*/
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
1.2 addWaiter()
/**
* 在隊列中新增一個節(jié)點。
*/
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
// 快速嘗試
if (pred != null) {
node.prev = pred;
// 通過CAS在隊尾插入當前節(jié)點
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
// 初始情況或者在快速嘗試失敗后插入節(jié)點
enq(node);
return node;
}
/**
* 通過循環(huán)+CAS在隊列中成功插入一個節(jié)點后返回拱镐。
*/
private Node enq(final Node node) {
for (;;) {
Node t = tail;
// 初始化head和tail
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
/*
* AQS的精妙就是體現(xiàn)在很多細節(jié)的代碼艘款,比如需要用CAS往隊尾里增加一個元素
* 此處的else分支是先在CAS的if前設(shè)置node.prev = t,而不是在CAS成功之后再設(shè)置沃琅。
* 一方面是基于CAS的雙向鏈表插入目前沒有完美的解決方案哗咆,另一方面這樣子做的好處是:
* 保證每時每刻tail.prev都不會是一個null值,否則如果node.prev = t
* 放在下面if的里面益眉,會導致一個瞬間tail.prev = null晌柬,這樣會使得隊列不完整。
*/
node.prev = t;
// CAS設(shè)置tail為node郭脂,成功后把老的tail也就是t連接到node年碘。
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
1.3 acquireQueued()
/**
* 在隊列中的節(jié)點通過此方法獲取鎖,對中斷不敏感展鸡。
*/
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
final Node p = node.predecessor();
/*
* 檢測當前節(jié)點前驅(qū)是否head屿衅,這是試獲取鎖的資格。
* 如果是的話莹弊,則調(diào)用tryAcquire嘗試獲取鎖,
* 成功涤久,則將head置為當前節(jié)點。
*/
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
/*
* 如果未成功獲取鎖則根據(jù)前驅(qū)節(jié)點判斷是否要阻塞忍弛。
* 如果阻塞過程中被中斷响迂,則置interrupted標志位為true。
* shouldParkAfterFailedAcquire方法在前驅(qū)狀態(tài)不為SIGNAL的情況下都會循環(huán)重試獲取鎖细疚。
*/
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
1.4
/**
* 喚醒后繼線程蔗彤。
*/
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
// 嘗試將node的等待狀態(tài)置為0,這樣的話,后繼爭用線程可以有機會再嘗試獲取一次鎖。
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
Node s = node.next;
/*
* 這里的邏輯就是如果node.next存在并且狀態(tài)不為取消,則直接喚醒s即可
* 否則需要從tail開始向前找到node之后最近的非取消節(jié)點幕与。
*
* 這里為什么要從tail開始向前查找也是值得琢磨的:
* 如果讀到s == null挑势,不代表node就為tail,參考addWaiter以及enq函數(shù)中的我的注釋啦鸣。
* 不妨考慮到如下場景:
* 1. node某時刻為tail
* 2. 有新線程通過addWaiter中的if分支或者enq方法添加自己
* 3. compareAndSetTail成功
* 4. 此時這里的Node s = node.next讀出來s == null潮饱,但事實上node已經(jīng)不是tail,它有后繼了!
*/
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);
}
2.釋放獨占鎖
釋放獨占鎖诫给,首先會調(diào)用tryRelease,在完全釋放掉獨占鎖后香拉,這時后繼線程是可以取到獨占鎖的。
public final boolean release(int arg) {
if (tryRelease(arg)) {
/*
* 此時的head節(jié)點可能有3種情況:
* 1. null (AQS的head延遲初始化+無競爭的情況)
* 2. 當前線程在獲取鎖時new出來的節(jié)點通過setHead設(shè)置的
* 3. 由于通過tryRelease已經(jīng)完全釋放掉了獨占鎖中狂,有新的節(jié)點在acquireQueued中獲取到了獨占鎖凫碌,并設(shè)置了head
* 第三種情況可以再分為兩種情況:
* (一)時刻1:線程A通過acquireQueued,持鎖成功胃榕,set了head
* 時刻2:線程B通過tryAcquire試圖獲取獨占鎖失敗失敗盛险,進入acquiredQueued
* 時刻3:線程A通過tryRelease釋放了獨占鎖
* 時刻4:線程B通過acquireQueued中的tryAcquire獲取到了獨占鎖并調(diào)用setHead
* 時刻5:線程A讀到了此時的head實際上是線程B對應的node
* (二)時刻1:線程A通過tryAcquire直接持鎖成功,head為null
* 時刻2:線程B通過tryAcquire試圖獲取獨占鎖失敗失敗勋又,入隊過程中初始化了head苦掘,進入acquiredQueued
* 時刻3:線程A通過tryRelease釋放了獨占鎖,此時線程B還未開始tryAcquire
* 時刻4:線程A讀到了此時的head實際上是線程B初始化出來的傀儡head
*/
Node h = head;
// head節(jié)點狀態(tài)不會是CANCELLED楔壤,所以這里h.waitStatus != 0相當于h.waitStatus < 0
if (h != null && h.waitStatus != 0)
// 喚醒后繼線程鹤啡,此函數(shù)在acquire中已經(jīng)分析過,不再列舉說明
unparkSuccessor(h);
return true;
}
return false;
}
總結(jié)一下上面的邏輯:
1.調(diào)用tryRelease
2.如果tryRelease返回true也就是獨占鎖被完全釋放蹲嚣,喚醒后繼線程