前言
線程并發(fā)系列文章:
Java 線程基礎(chǔ)
Java 線程狀態(tài)
Java “優(yōu)雅”地中斷線程-實(shí)踐篇
Java “優(yōu)雅”地中斷線程-原理篇
真正理解Java Volatile的妙用
Java ThreadLocal你之前了解的可能有誤
Java Unsafe/CAS/LockSupport 應(yīng)用與原理
Java 并發(fā)"鎖"的本質(zhì)(一步步實(shí)現(xiàn)鎖)
Java Synchronized實(shí)現(xiàn)互斥之應(yīng)用與源碼初探
Java 對象頭分析與使用(Synchronized相關(guān))
Java Synchronized 偏向鎖/輕量級鎖/重量級鎖的演變過程
Java Synchronized 重量級鎖原理深入剖析上(互斥篇)
Java Synchronized 重量級鎖原理深入剖析下(同步篇)
Java并發(fā)之 AQS 深入解析(上)
Java并發(fā)之 AQS 深入解析(下)
Java Thread.sleep/Thread.join/Thread.yield/Object.wait/Condition.await 詳解
Java 并發(fā)之 ReentrantLock 深入分析(與Synchronized區(qū)別)
Java 并發(fā)之 ReentrantReadWriteLock 深入分析
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(原理篇)
Java Semaphore/CountDownLatch/CyclicBarrier 深入解析(應(yīng)用篇)
最詳細(xì)的圖文解析Java各種鎖(終極篇)
線程池必懂系列
前面幾篇分析了synchronized 原理及其使用琐簇,synchronized 是JVM實(shí)現(xiàn)的,核心代碼是C++座享,對于不熟悉C++語言的讀者可能有點(diǎn)難度婉商。JUC 包下提供了新的同步框架:AQS,是純JAVA代碼實(shí)現(xiàn)的渣叛。如果你了解了synchronized 核心丈秩,那么AQS不在話下,若是不了解淳衙,本篇將一起從頭到尾深入分析AQS蘑秽。
通過本篇文章饺著,你將了解到:
1、如何實(shí)現(xiàn)自己的同步框架
2肠牲、AQS 功能解析
3幼衰、AQS 獨(dú)占鎖實(shí)現(xiàn)
4、AQS 共享鎖實(shí)現(xiàn)
5埂材、場景模擬與疑難解答
1塑顺、如何實(shí)現(xiàn)自己的同步框架
準(zhǔn)備關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
第一
得需要共享變量作為"鎖芯"。由于這個(gè)共享變量是多線程共享俏险,為保證線程間的可見性严拒,因此需要用volatile關(guān)鍵字修飾。
第二
當(dāng)線程競爭鎖成功時(shí)則進(jìn)入臨界區(qū)執(zhí)行代碼竖独,當(dāng)失敗時(shí)需要加入到隊(duì)列里進(jìn)行等待裤唠,因此需要一個(gè)同步隊(duì)列,用以存放因獲取鎖失敗而掛起的線程莹痢。
第三
線程之間需要同步种蘸,A線程等待B線程生產(chǎn)數(shù)據(jù),B線程生產(chǎn)了數(shù)據(jù)通知A線程竞膳,因此需要一個(gè)等待(條件)隊(duì)列航瞭。
核心操作
數(shù)據(jù)結(jié)構(gòu)準(zhǔn)備好之后,需要操作以上數(shù)據(jù)結(jié)構(gòu)來實(shí)現(xiàn)鎖功能坦辟。
線程競爭鎖:通過CAS操作"鎖芯"刊侯,操作成功則執(zhí)行臨界區(qū)代碼,失敗則加入到同步隊(duì)列锉走。
線程被喚醒:拿到鎖的線程執(zhí)行完臨界區(qū)代碼后釋放鎖滨彻,并喚醒同步隊(duì)列里等待的線程,被喚醒的線程繼續(xù)競爭鎖挪蹭。
線程等待某個(gè)條件:線程因?yàn)槟撤N條件不滿足于是加入到等待隊(duì)列里亭饵,釋放鎖,并掛起等待梁厉。
條件滿足線程被喚醒:條件滿足辜羊,線程被喚醒后繼續(xù)競爭鎖。
以上步驟是實(shí)現(xiàn)鎖功能的核心词顾,不論是synchronized還是AQS只冻,基礎(chǔ)功能都是以上步驟,只是他們還擁有更豐富的功能计技,如可中斷(AQS)喜德,可重入、可獨(dú)占可共享(AQS)等垮媒。
自己實(shí)現(xiàn)鎖的實(shí)踐請移步:Java 并發(fā)"鎖"的本質(zhì)(一步步實(shí)現(xiàn)鎖)
2舍悯、AQS 功能解析
AQS是AbstractQueuedSynchronizer的簡稱航棱,顧名思義:同步器。它是JUC下的同步框架萌衬,也是實(shí)現(xiàn)鎖的核心類饮醇。
AQS是抽象類,提供了基礎(chǔ)的方法秕豫,需要子類實(shí)現(xiàn)具體的獲取鎖朴艰、釋放鎖操作。
如上圖所示混移,AQS 實(shí)現(xiàn)了獨(dú)占鎖/共享鎖祠墅、可中斷鎖/不可中斷鎖的邏輯,當(dāng)子類擴(kuò)展AQS時(shí)調(diào)用對應(yīng)的方法即可實(shí)現(xiàn)不同的鎖組合歌径。
JUC下擴(kuò)展自AQS的子類封裝器:
接下來進(jìn)入到AQS源碼里毁嗦,看看它是如何實(shí)現(xiàn)上述功能的。
注:Semaphore和CountDownLatch 并不是嚴(yán)格意義上的鎖回铛,后面具體分析每種鎖的時(shí)候再細(xì)說
3狗准、AQS 獨(dú)占鎖實(shí)現(xiàn)
A、先找到關(guān)鍵數(shù)據(jù)結(jié)構(gòu)
鎖芯
#AbstractQueuedSynchronizer.java
private volatile int state;
state 稱為共享資源或者同步狀態(tài)茵肃,作為鎖的鎖芯腔长,可以看出它用volatile修飾了。
同步隊(duì)列
#AbstractQueuedSynchronizer.java
//指向同步隊(duì)列的頭
private transient volatile Node head;
//指向同步隊(duì)列的尾
private transient volatile Node tail;
再來看Node里的元素:
#AbstractQueuedSynchronizer.java
static final class Node {
...
//前驅(qū)節(jié)點(diǎn)
volatile Node prev;
//后繼節(jié)點(diǎn)
volatile Node next;
//占用獨(dú)占鎖的線程
volatile Thread thread;
//指向下一個(gè)等待條件的節(jié)點(diǎn)
Node nextWaiter;
...
}
B验残、操作關(guān)鍵數(shù)據(jù)結(jié)構(gòu)的方法
1捞附、先說獲取同步狀態(tài)的操作
acquire(xx)
#AbstractQueuedSynchronizer.java
public final void acquire(int arg) {
//arg 不同的鎖實(shí)現(xiàn)有不同的含義
//tryAcquire 由子類實(shí)現(xiàn)具體的獲取同步狀態(tài)操作
//addWaiter 將當(dāng)前線程封裝在Node里并加入到同步隊(duì)列
//acquireQueued 符合條件則掛起線程
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
//補(bǔ)上中斷標(biāo)記
selfInterrupt();//-------------(1)
}
里面寫法很簡單,重要工作都在各個(gè)方法里體現(xiàn)胚膊。
tryAcquire(xx)
真正獲取鎖的地方紊婉,也就是操作鎖芯"state"的地方,不同子類有不一樣的實(shí)現(xiàn)辑舷,后面會分類細(xì)說喻犁。tryAcquire(xx) 返回true表示獲取同步狀態(tài)成功,false表示獲取同步狀態(tài)失敗何缓。
addWaiter(xx)
#AbstractQueuedSynchronizer.java
private Node addWaiter(Node mode) {
//構(gòu)造新節(jié)點(diǎn)
Node node = new Node(Thread.currentThread(), mode);
Node pred = tail;
if (pred != null) {
//尾節(jié)點(diǎn)存在
//新節(jié)點(diǎn)的前驅(qū)指針指向尾節(jié)點(diǎn)
node.prev = pred;//-------------(2)
//CAS修改為尾節(jié)點(diǎn)指向新節(jié)點(diǎn)
if (compareAndSetTail(pred, node)) {
//成功后
//將最后一個(gè)節(jié)點(diǎn)的后繼指針指向新加入的節(jié)點(diǎn)
//此時(shí)新節(jié)點(diǎn)正式加入到同步隊(duì)列里了
pred.next = node;
return node;
}
}
//前面步驟加入隊(duì)列失敗肢础,則會走到這
enq(node);
//返回新加入的節(jié)點(diǎn)
return node;
}
private Node enq(final Node node) {
//死循環(huán)務(wù)必保證插入隊(duì)列成功
for (;;) {
Node t = tail;
if (t == null) {
//隊(duì)列是空的,則先創(chuàng)建頭節(jié)點(diǎn)
if (compareAndSetHead(new Node()))
//尾節(jié)點(diǎn)指向頭節(jié)點(diǎn)
tail = head;
} else {
//和addWaiter里一樣的操作碌廓,加入新節(jié)點(diǎn)到隊(duì)尾
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
addWaiter(xx)作用是將節(jié)點(diǎn)加入到同步隊(duì)列的尾部传轰。
需要注意的是:
頭節(jié)點(diǎn)不關(guān)聯(lián)任何線程,僅僅起到索引的作用谷婆。
最終慨蛙,同步隊(duì)列如下圖:
acquireQueued(xx)
按照以往的經(jīng)驗(yàn)(synchronized)期贫,加入到同步隊(duì)列后應(yīng)該掛起線程跟匆,來看看AQS實(shí)現(xiàn)有何不同:
#AbstractQueuedSynchronizer.java
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
//記錄中斷標(biāo)志
boolean interrupted = false;
for (;;) {
//找到當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
final Node p = node.predecessor();
//如果前驅(qū)節(jié)點(diǎn)為頭節(jié)點(diǎn),則嘗試獲取同步狀態(tài)
if (p == head && tryAcquire(arg)) {
//獲取同步狀態(tài)成功通砍,將頭節(jié)點(diǎn)指向下一個(gè)節(jié)點(diǎn)玛臂,并且新的頭節(jié)點(diǎn)prev=null,表示原本的頭節(jié)點(diǎn)出隊(duì)了
setHead(node);
//原本的頭節(jié)點(diǎn)next=null封孙,幫助盡快GC
p.next = null;
failed = false;
return interrupted;
}
//判斷獲取同步狀態(tài)失敗后是否需要掛起迹冤,走到這里說明獲取同步狀態(tài)失敗了,可能需要掛起
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
//標(biāo)記中斷
interrupted = true;
}
} finally {
if (failed)
//還是沒獲取成功敛瓷,則取消競爭同步狀態(tài)操作
cancelAcquire(node);
}
}
這里面的邏輯可能比較繞叁巨,著重分析一下。
首先外層有個(gè)死循環(huán)呐籽,該循環(huán)退出的條件是當(dāng)前線程成功獲取了同步狀態(tài)锋勺。
其次,如果當(dāng)前新加入隊(duì)列的節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)是頭節(jié)點(diǎn)狡蝶,那么它就會去嘗試獲取同步狀態(tài)庶橱。若是獲取同步狀態(tài)失敗或者它的前驅(qū)節(jié)點(diǎn)不是頭節(jié)點(diǎn),則進(jìn)入到shouldParkAfterFailedAcquire(xx)方法贪惹。
#AbstractQueuedSynchronizer.java
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//拿出前驅(qū)節(jié)點(diǎn)的狀態(tài)
int ws = pred.waitStatus;
if (ws == Node.SIGNAL)
//為SIGNAL 直接返回
return true;
if (ws > 0) {
//該節(jié)點(diǎn)已被取消
do {
//一直往前追溯苏章,直到找到不被取消的節(jié)點(diǎn)為止
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
//找到
pred.next = node;
} else {
//不是取消狀態(tài)阳似,則直接設(shè)置前驅(qū)節(jié)點(diǎn)狀態(tài)為SIGNAL
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
該方法返回true胡野,則說明當(dāng)前線程所在節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)的狀態(tài)為:SIGNAL树酪。進(jìn)而執(zhí)行parkAndCheckInterrupt()方法蔬咬。
parkAndCheckInterrupt()
從名字就可以看出來是掛起線程并檢查中斷殉疼。
#AbstractQueuedSynchronizer.java
private final boolean parkAndCheckInterrupt() {
//掛起線程
LockSupport.park(this);
//查詢中斷標(biāo)記位
return Thread.interrupted();
}
cancelAcquire(xx)
該方法有兩種場景會調(diào)用到:
1亚兄、當(dāng)獲取同步狀態(tài)發(fā)生異常時(shí)醋安,需要取消線程競爭同步狀態(tài)的操作假丧。
2珍昨、當(dāng)獲取同步狀態(tài)的超時(shí)時(shí)間到來之時(shí)县耽,若此刻還無法成功獲取同步狀態(tài),則調(diào)用該方法镣典。
#AbstractQueuedSynchronizer.java
private void cancelAcquire(Node node) {
if (node == null)
return;
node.thread = null;
Node pred = node.prev;
//若是已經(jīng)取消了兔毙,則跳過
while (pred.waitStatus > 0)
node.prev = pred = pred.prev;
Node predNext = pred.next;
//標(biāo)記為取消狀態(tài)
node.waitStatus = Node.CANCELLED;
//如果當(dāng)前節(jié)點(diǎn)是尾節(jié)點(diǎn),則將尾節(jié)點(diǎn)指向當(dāng)前節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)
if (node == tail && compareAndSetTail(node, pred)) {
//再將前驅(qū)節(jié)點(diǎn)的后繼指針置為空兄春,把node從隊(duì)列里移除
compareAndSetNext(pred, predNext, null);
} else {//---------------(3)
int ws;
if (pred != head &&
((ws = pred.waitStatus) == Node.SIGNAL ||
(ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
pred.thread != null) {
//若前驅(qū)節(jié)點(diǎn)不是頭結(jié)點(diǎn)澎剥,當(dāng)前節(jié)點(diǎn)也不是尾結(jié)點(diǎn)
Node next = node.next;
if (next != null && next.waitStatus <= 0)
//將node的前驅(qū)節(jié)點(diǎn)的后繼指針指向node的后繼節(jié)點(diǎn)
compareAndSetNext(pred, predNext, next);
} else {
//前驅(qū)節(jié)點(diǎn)是頭結(jié)點(diǎn)
unparkSuccessor(node);
}
node.next = node; // help GC
}
}
2哑姚、再說釋放同步狀態(tài)的操作
release(xx)
#AbstractQueuedSynchronizer.java
public final boolean release(int arg) {
//tryRelease 釋放同步狀態(tài)
if (tryRelease(arg)) {
//釋放同步狀態(tài)成功
Node h = head;
if (h != null && h.waitStatus != 0)
//waitStatus 不為0趾唱,則喚醒線程
unparkSuccessor(h);
return true;
}
return false;
}
private void unparkSuccessor(Node node) {
int ws = node.waitStatus;
//將頭結(jié)點(diǎn)狀態(tài)置為0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//取出頭結(jié)點(diǎn)的后繼節(jié)點(diǎn)
Node s = node.next;
if (s == null || s.waitStatus > 0) {
//若是沒有后繼節(jié)點(diǎn)或者是取消狀態(tài)
s = null;
//則從尾部開始尋找離頭結(jié)點(diǎn)最近的未取消的節(jié)點(diǎn)-----------(4)
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//喚醒線程
if (s != null)
LockSupport.unpark(s.thread);
}
節(jié)點(diǎn)狀態(tài)
#AbstractQueuedSynchronizer.java
//節(jié)點(diǎn)被取消甜癞,不再參與競爭鎖
static final int CANCELLED = 1;
//表示該節(jié)點(diǎn)的后繼節(jié)點(diǎn)需要喚醒
static final int SIGNAL = -1;
//節(jié)點(diǎn)在等待隊(duì)列里的狀態(tài)
static final int CONDITION = -2;
//表示頭結(jié)點(diǎn)將喚醒的動作傳播下去
static final int PROPAGATE = -3;
//默認(rèn)值為0
至此,獨(dú)占鎖的獲取鎖宛乃、釋放鎖的流程已經(jīng)分析完畢悠咱,如下圖:
4、AQS 共享鎖實(shí)現(xiàn)
獨(dú)占鎖是同一時(shí)刻只允許一個(gè)線程獲取鎖征炼,而共享鎖則不然析既,來看看AQS里共享鎖的實(shí)現(xiàn)。
先來看看獲取共享同步狀態(tài)的操作
acquireShared(xx)
#AbstractQueuedSynchronizer.java
public final void acquireShared(int arg) {
//獲取共享的同步狀態(tài)谆奥,不同鎖實(shí)現(xiàn)不一樣
//<0 表示獲取同步狀態(tài)失敗
if (tryAcquireShared(arg) < 0)
//加入同步隊(duì)列眼坏、掛起線程等在此處實(shí)現(xiàn)
doAcquireShared(arg);
}
與獨(dú)占鎖的獲取不一樣的是,此處將加入同步隊(duì)列與掛起線程等操作放到一個(gè)方法里了酸些。
doAcquireShared(xx)
#AbstractQueuedSynchronizer.java
private void doAcquireShared(int arg) {
//加入同步隊(duì)列宰译,此處節(jié)點(diǎn)是共享狀態(tài)
final Node node = addWaiter(Node.SHARED);
//獲取同步狀態(tài)失敗
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//前驅(qū)節(jié)點(diǎn)
final Node p = node.predecessor();
if (p == head) {
//若是前驅(qū)節(jié)點(diǎn)為頭結(jié)點(diǎn),則嘗試獲取同步狀態(tài)
int r = tryAcquireShared(arg);
if (r >= 0) {
//獲取同步狀態(tài)成功
//修改頭結(jié)點(diǎn)魄懂,并傳遞喚醒狀態(tài)
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
//補(bǔ)中斷
selfInterrupt();
failed = false;
return;
}
}
//與獨(dú)占鎖一致
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
tryAcquireShared(arg) 返回值表示當(dāng)前可用的資源沿侈。
setHeadAndPropagate(xx)
#AbstractQueuedSynchronizer.java
private void setHeadAndPropagate(Node node, int propagate) {
//propagate == 0 表示沒有資源可以使用了
Node h = head; // Record old head for check below
//設(shè)置頭結(jié)點(diǎn)
setHead(node);
if (propagate > 0 || h == null || h.waitStatus < 0 ||
(h = head) == null || h.waitStatus < 0) {
Node s = node.next;
//若后繼節(jié)點(diǎn)是共享節(jié)點(diǎn),則喚醒
if (s == null || s.isShared())
doReleaseShared();
}
除了將頭結(jié)點(diǎn)指向當(dāng)前節(jié)點(diǎn)外市栗,還需要喚醒下一個(gè)共享節(jié)點(diǎn)缀拭。
而獨(dú)占鎖不會。
從現(xiàn)實(shí)的角度來看也比較容易理解這種操作:
某個(gè)澡堂分男女分批洗填帽,當(dāng)前只允許女士進(jìn)去先洗蛛淋,而一群男士在排隊(duì)等候,當(dāng)女士們洗好之后篡腌,允許男士進(jìn)去洗褐荷。第一個(gè)男士進(jìn)去后,發(fā)現(xiàn)可以洗哀蘑,于是跟第二個(gè)男士說可以洗诚卸,你快進(jìn)來吧葵第,真可以洗绘迁,這是共享精神。這個(gè)澡堂就是共享的卒密。
再來看看釋放共享同步狀態(tài)的操作
releaseShared(xx)
#AbstractQueuedSynchronizer.java
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
//釋放同步狀態(tài)成功后缀台,通知后續(xù)節(jié)點(diǎn)
doReleaseShared();
return true;
}
return false;
}
可以看出,線程在獲取共享鎖和釋放共享鎖后都會嘗試喚醒后續(xù)節(jié)點(diǎn)哮奇,都調(diào)用了
doReleaseShared()方法膛腐。
doReleaseShared()
#AbstractQueuedSynchronizer.java
private void doReleaseShared() {
for (;;) {
Node h = head;
if (h != null && h != tail) {
//隊(duì)列里還有節(jié)點(diǎn)等候
int ws = h.waitStatus;
if (ws == Node.SIGNAL) {
//喚醒后繼節(jié)點(diǎn)
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
unparkSuccessor(h);
}
//將頭節(jié)點(diǎn)狀態(tài)置為PROPAGATE-------->(5)
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//該方法可能會被多個(gè)線程調(diào)用睛约,而線程獲取鎖后會修改頭節(jié)點(diǎn)
//因此,若是發(fā)現(xiàn)頭結(jié)點(diǎn)更改了哲身,則再重新拿新的頭結(jié)點(diǎn)再試探
if (h == head) // loop if head changed
break;
}
}
至此勘天,共享鎖的獲取鎖怔揩、釋放鎖的流程已經(jīng)分析完畢,如下圖:
5脯丝、場景模擬與疑難分析
共享鎖商膊、獨(dú)占鎖的實(shí)現(xiàn)重要方法、數(shù)據(jù)結(jié)構(gòu)都過了一遍宠进,接下來通過模擬場景來分析上面提到的五個(gè)問題晕拆。
1、問:為什么需要selfInterrupt()
#AbstractQueuedSynchronizer.java
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
LockSupport.park(this)將線程掛起材蹬,掛起后調(diào)用Thread.interrupted()查詢中斷狀態(tài)实幕。而Thread.interrupted()除了查詢中斷狀態(tài)外,還會重置中斷狀態(tài)堤器,也就是說之前中斷狀態(tài)為true茬缩,調(diào)用該方法后中斷狀態(tài)變?yōu)閒alse。而從整個(gè)acquire(xx)方法來看吼旧,沒有任何地方處理了中斷,因此不能簡單將中斷狀態(tài)置為false圈暗,還需要恢復(fù)到原來的樣子掂为,讓外部調(diào)用者可以感知到是否已經(jīng)發(fā)生過中斷了,所以需要selfInterrupt() 重新把中斷狀態(tài)設(shè)置為true员串。
既然后面要恢復(fù)中斷狀態(tài)勇哗,那干嘛一開始就置為false呢,直接調(diào)用Thread.isInterrupted()不就ok了嗎寸齐?
想象一種場景:A線程被中斷欲诺,此時(shí)從掛起狀態(tài)醒過來,然后去獲取同步狀態(tài)渺鹦,發(fā)現(xiàn)還是無法獲取扰法,于是又開始準(zhǔn)備掛起了。此處掛起線程使用的是LockSupport.park(xx)方法毅厚,其底層使用Parker.park(xx)函數(shù):
注意紅框里代碼塞颁,若是發(fā)現(xiàn)當(dāng)前線程中斷狀態(tài)為true,則直接返回不再掛起線程。若是調(diào)用Thread.isInterrupted()祠锣,中斷狀態(tài)沒有改為false酷窥,那么當(dāng)調(diào)用LockSupport.park(xx)方法時(shí),線程是無法掛起的伴网。而acquire(xx)方法里沒有獲取到鎖就一直循環(huán)蓬推,導(dǎo)致線程一直不斷輪詢同步狀態(tài),造成了不必要的CPU資源浪費(fèi)澡腾。
Parker細(xì)節(jié)請移步:Java Unsafe/CAS/LockSupport 應(yīng)用與原理
線程中斷細(xì)節(jié)請移步:Java “優(yōu)雅”地中斷線程(原理篇)
2拳氢、問:為什么先設(shè)置前驅(qū)指針
當(dāng)前的順序是:
node.prev = pred------>pred.next = node;
先讓新結(jié)點(diǎn)的prev指向尾結(jié)點(diǎn),再讓尾結(jié)點(diǎn)的next指向新結(jié)點(diǎn)蛋铆,如下圖:
現(xiàn)在同步隊(duì)列里有兩個(gè)結(jié)點(diǎn)馋评,其中一個(gè)頭結(jié)點(diǎn),一個(gè)是Node1結(jié)點(diǎn)刺啦。若是先給pred.next 賦值留特,假設(shè)流程如下:
1、線程A先競爭鎖玛瘸,競爭失敗蜕青,先將Node1的next指向NewNodeA。
2糊渊、此時(shí)另一個(gè)線程B也來競爭鎖右核,失敗,也將Node1的next指向NewNodeB渺绒。
3贺喝、將tail指針指向新的節(jié)點(diǎn)(可能是NewNodeA,也可能是NewNodeB)宗兼,若是NewNodeA躏鱼,然后將NewNodeA的prev指向Node1。此時(shí)問題出現(xiàn)了:雖然NewNodeA的prev指向了Node1殷绍,但是Node1的next卻是指向了NewNodeB染苛。
而先給node.prev 賦值就不會出現(xiàn)上述情況。出現(xiàn)這個(gè)問題的根本原因是多線程操作隊(duì)列元素(給Node1.next賦值)沒有做好并發(fā)保護(hù)主到,而先給node.prev 并不是操作隊(duì)列茶行,將操作隊(duì)列的步驟延遲到CAS成功之后,就能正確地修改隊(duì)列登钥。
當(dāng)然畔师,pred.next = node 執(zhí)行之前,其它線程可能會遍歷查詢隊(duì)列怔鳖,此時(shí)pred.next可能為空茉唉,也就是上圖的Node1.next可能為空。
這也是網(wǎng)上一些文章說next指針不可靠的原因结执。
3度陆、問:node 什么時(shí)候從隊(duì)列里移除
cancelAcquire(xx)分三種情形操作同步隊(duì)列:
1、若node為隊(duì)尾節(jié)點(diǎn)献幔,則將node從隊(duì)列移除懂傀。
2、若node為隊(duì)頭節(jié)點(diǎn)蜡感,則調(diào)用unparkSuccessor(xx)檢測蹬蚁。
3、若node為中間節(jié)點(diǎn)郑兴,則在shouldParkAfterFailedAcquire(xx)/unparkSuccessor(xx) 徹底移除犀斋。
4、問:為什么需要從尾部開始索引
在第2點(diǎn)里有分析過節(jié)點(diǎn)的next指針可能為空情连,若是從隊(duì)頭開始索引叽粹,有可能還沒遍歷完整個(gè)隊(duì)列就退出遍歷了。因此却舀,為了保險(xiǎn)起見虫几,從隊(duì)尾開始索引。
5挽拔、問:為什么需要PROPAGATE狀態(tài)
PROPAGATE 在共享節(jié)點(diǎn)時(shí)才用得到辆脸,假設(shè)現(xiàn)在有4個(gè)線程、A螃诅、B啡氢、C、D术裸,A/B 先嘗試獲取鎖空执,沒有成功則將自己掛起,C/D 釋放鎖穗椅”姘恚可以參照Semaphore獲取/釋放鎖流程。
1匹表、C 釋放鎖后state=1门坷,設(shè)置head.waitStatus=0,然后將A喚醒袍镀,A醒過來后調(diào)用tryAcquireShared(xx)默蚌,該方法返回r=0,此時(shí)state=0苇羡。
2绸吸、在A還沒調(diào)用setHeadAndPropagate(xx)之前,D 釋放了鎖,此時(shí)D調(diào)用doReleaseShared()锦茁,發(fā)現(xiàn)head.waitStatus==0攘轩,所以沒有喚醒其它節(jié)點(diǎn)。
3码俩、此時(shí)A調(diào)用了setHeadAndPropagate(xx)度帮,因?yàn)閞==0且head.waitStatus==0,因此不會調(diào)用doReleaseShared()稿存,也就沒有喚醒其它節(jié)點(diǎn)笨篷。最后導(dǎo)致的是B節(jié)點(diǎn)沒有被喚醒。
若是加了PROPAGATE狀態(tài)瓣履,在上面的第2步驟里的D調(diào)用doReleaseShared()后率翅,發(fā)現(xiàn)head.waitStatus==0,于是設(shè)置head.waitStatus=PROPAGATE袖迎,在第3步驟里安聘,發(fā)現(xiàn)head.waitStatus==PROPAGATE,于是喚醒B瓢棒。
雖然在第2步驟里沒有喚醒任何線程浴韭,但是設(shè)置了PROPAGATE狀態(tài),在后續(xù)的步驟中發(fā)現(xiàn)已經(jīng)設(shè)置了PROPAGATE脯宿,于是喚醒念颈,這也是PROPAGATE名字的意義:傳播。
由于篇幅原因连霉,下篇將分析AQS 中斷與否榴芳、條件等待等相關(guān)知識。
本文基于jdk1.8跺撼。