基于jdk8
越是核心的東西越是要反復(fù)看,本文篇幅較長,希望各位細細品讀掖蛤,來回多讀幾遍理解下。
AQS簡介
??java的內(nèi)置鎖一直都是備受爭議的井厌,在JDK6之前蚓庭,synchronized這個重量級鎖其性能一直都是較為低下,雖然在JDK6后旗笔,進行大量的鎖優(yōu)化策略,但是與Lock相比synchronized還是存在一些缺陷的:雖然synchronized提供了便捷性的隱式獲取鎖釋放鎖機制(基于JVM機制)彪置,但是它卻缺少了獲取鎖與釋放鎖的可操作性拄踪,可中斷蝇恶、超時獲取鎖,且它為獨占式在高并發(fā)場景下性能大打折扣惶桐。
??在介紹Lock之前撮弧,我們需要先熟悉一個非常重要的組件,掌握了該組件JUC包下面很多問題都不在是問題了姚糊。該組件就是AQS贿衍。
??AQS:AbstractQueuedSynchronizer,即隊列同步器救恨。它是構(gòu)建鎖或者其他同步組件的基礎(chǔ)框架(如ReentrantLock贸辈、ReentrantReadWriteLock、Semaphore等)肠槽,JUC并發(fā)包的作者(Doug Lea)期望它能夠成為實現(xiàn)大部分同步需求的基礎(chǔ)擎淤。它是JUC并發(fā)包中的核心基礎(chǔ)組件。
??AQS解決了子類實現(xiàn)同步器時涉及當?shù)拇罅考毠?jié)問題秸仙,例如獲取同步狀態(tài)嘴拢、FIFO同步隊列〖偶停基于AQS來構(gòu)建同步器可以帶來很多好處席吴。它不僅能夠極大地減少實現(xiàn)工作,而且也不必處理在多個位置上發(fā)生的競爭問題捞蛋。
??在基于AQS構(gòu)建的同步器中孝冒,只能在一個時刻發(fā)生阻塞,從而降低上下文切換的開銷拟杉,提高了吞吐量庄涡。同時在設(shè)計AQS時充分考慮了可伸縮行,因此J.U.C中所有基于AQS構(gòu)建的同步器均可以獲得這個優(yōu)勢捣域。
??AQS的主要使用方式是繼承啼染,子類通過繼承同步器并實現(xiàn)它的抽象方法來管理同步狀態(tài)宴合。
??AQS使用一個int類型的成員變量state來表示同步狀態(tài),當state>0時表示已經(jīng)獲取了鎖迹鹅,當state = 0時表示釋放了鎖卦洽。它提供了三個方法(getState()、setState(int newState)斜棚、compareAndSetState(int expect,int update))來對同步狀態(tài)state進行操作阀蒂,當然AQS可以確保對state的操作是安全的。
??AQS通過內(nèi)置的FIFO同步隊列來完成資源獲取線程的排隊工作弟蚀,如果當前線程獲取同步狀態(tài)失斣橄肌(鎖)時,AQS則會將當前線程以及等待狀態(tài)等信息構(gòu)造成一個節(jié)點(Node)并將其加入同步隊列义钉,同時會阻塞當前線程昧绣,當同步狀態(tài)釋放時,則會把節(jié)點中的線程喚醒捶闸,使其再次嘗試獲取同步狀態(tài)夜畴。
AQS主要提供了如下一些方法:
- getState():返回同步狀態(tài)的當前值;
- setState(int newState):設(shè)置當前同步狀態(tài)删壮;
- compareAndSetState(int expect, int update):使用CAS設(shè)置當前狀態(tài)贪绘,該方法能夠保證狀態(tài)設(shè)置的原子性;
- tryAcquire(int arg):獨占式獲取同步狀態(tài)央碟,獲取同步狀態(tài)成功后税灌,其他線程需要等待該線程釋放同步狀態(tài)才能獲取同步狀態(tài)
- tryRelease(int arg):獨占式釋放同步狀態(tài);
- tryAcquireShared(int arg):共享式獲取同步狀態(tài)亿虽,返回值大于等于0則表示獲取成功菱涤,否則獲取失敗经柴;
- tryReleaseShared(int arg):共享式釋放同步狀態(tài)狸窘;
- isHeldExclusively():當前同步器是否在獨占式模式下被線程占用,一般該方法表示是否被當前線程所獨占坯认;
- acquire(int arg):獨占式獲取同步狀態(tài)翻擒,如果當前線程獲取同步狀態(tài)成功,則由該方法返回牛哺,否則陋气,將會進入同步隊列等待,該方法將會調(diào)用可重寫的tryAcquire(int arg)方法引润;
- acquireInterruptibly(int arg):與acquire(int arg)相同巩趁,但是該方法響應(yīng)中斷,當前線程為獲取到同步狀態(tài)而進入到同步隊列中,如果當前線程被中斷议慰,則該方法會拋出InterruptedException異常并返回蠢古;
- tryAcquireNanos(int arg,long nanos):超時獲取同步狀態(tài),如果當前線程在nanos時間內(nèi)沒有獲取到同步狀態(tài)别凹,那么將會返回false草讶,已經(jīng)獲取則返回true;
- acquireShared(int arg):共享式獲取同步狀態(tài)炉菲,如果當前線程未獲取到同步狀態(tài)堕战,將會進入同步隊列等待,與獨占式的主要區(qū)別是在同一時刻可以有多個線程獲取到同步狀態(tài)拍霜;
- acquireSharedInterruptibly(int arg):共享式獲取同步狀態(tài)嘱丢,響應(yīng)中斷;
- tryAcquireSharedNanos(int arg, long nanosTimeout):共享式獲取同步狀態(tài)祠饺,增加超時限制越驻;
- release(int arg):獨占式釋放同步狀態(tài),該方法會在釋放同步狀態(tài)之后吠裆,將同步隊列中第一個節(jié)點包含的線程喚醒伐谈;
- releaseShared(int arg):共享式釋放同步狀態(tài);
CLH同步隊列
??CLH同步隊列是一個FIFO雙向隊列试疙,AQS依賴它來完成同步狀態(tài)的管理,當前線程如果獲取同步狀態(tài)失敗時抠蚣,AQS則會將當前線程已經(jīng)等待狀態(tài)等信息構(gòu)造成一個節(jié)點(Node)并將其加入到CLH同步隊列祝旷,同時會阻塞當前線程,當同步狀態(tài)釋放時嘶窄,會把首節(jié)點喚醒(公平鎖)怀跛,使其再次嘗試獲取同步狀態(tài)。
??在CLH同步隊列中柄冲,一個節(jié)點表示一個線程吻谋,它保存著線程的引用(thread)、狀態(tài)(waitStatus)现横、前驅(qū)節(jié)點(prev)漓拾、后繼節(jié)點(next),其定義如下:
static final class Node {
/** 共享 */
static final Node SHARED = new Node();
/** 獨占 */
static final Node EXCLUSIVE = null;
/**
* 因為超時或者中斷戒祠,節(jié)點會被設(shè)置為取消狀態(tài)骇两,被取消的節(jié)點時不會參與到競爭中的,他會一直保持取消狀態(tài)不會轉(zhuǎn)變?yōu)槠渌麪顟B(tài)姜盈;
*/
static final int CANCELLED = 1;
/**
* 后繼節(jié)點的線程處于等待狀態(tài)低千,而當前節(jié)點的線程如果釋放了同步狀態(tài)或者被取消,將會通知后繼節(jié)點馏颂,使后繼節(jié)點的線程得以運行
*/
static final int SIGNAL = -1;
/**
* 節(jié)點在等待隊列中示血,節(jié)點線程等待在Condition上棋傍,當其他線程對Condition調(diào)用了signal()后,該節(jié)點將會從等待隊列中轉(zhuǎn)移到同步隊列中难审,加入到同步狀態(tài)的獲取中
*/
static final int CONDITION = -2;
/**
* 表示下一次共享式同步狀態(tài)獲取將會無條件地傳播下去
*/
static final int PROPAGATE = -3;
/** 等待狀態(tài) */
volatile int waitStatus;
/** 前驅(qū)節(jié)點 */
volatile Node prev;
/** 后繼節(jié)點 */
volatile Node next;
/** 獲取同步狀態(tài)的線程 */
volatile Thread thread;
Node nextWaiter;
final boolean isShared() {
return nextWaiter == SHARED;
}
final Node predecessor() throws NullPointerException {
Node p = prev;
if (p == null)
throw new NullPointerException();
else
return p;
}
Node() {
}
Node(Thread thread, Node mode) {
this.nextWaiter = mode;
this.thread = thread;
}
Node(Thread thread, int waitStatus) {
this.waitStatus = waitStatus;
this.thread = thread;
}
}
CLH同步隊列結(jié)構(gòu)圖如下:
入列
??學(xué)了數(shù)據(jù)結(jié)構(gòu)的我們舍沙,CLH隊列入列是再簡單不過了,無非就是tail指向新節(jié)點剔宪、新節(jié)點的prev指向當前最后的節(jié)點拂铡,當前最后一個節(jié)點的next指向當前節(jié)點。代碼我們可以看看addWaiter(Node node)方法:
private Node addWaiter(Node mode) {
//新建Node
Node node = new Node(Thread.currentThread(), mode);
//快速嘗試添加尾節(jié)點
Node pred = tail;
if (pred != null) {
node.prev = pred;
//CAS設(shè)置尾節(jié)點
if (compareAndSetTail(pred, node)) {
pred.next = node;
return node;
}
}
//多次嘗試
enq(node);
return node;
}
??addWaiter(Node node)先通過快速嘗試設(shè)置尾節(jié)點葱绒,如果失敗感帅,則調(diào)用enq(Node node)方法設(shè)置尾節(jié)點
private Node enq(final Node node) {
//多次嘗試,直到成功為止
for (;;) {
Node t = tail;
//tail不存在地淀,設(shè)置為首節(jié)點
if (t == null) {
if (compareAndSetHead(new Node()))
tail = head;
} else {
//設(shè)置為尾節(jié)點
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
??在上面代碼中失球,兩個方法都是通過一個CAS方法compareAndSetTail(Node expect, Node update)來設(shè)置尾節(jié)點,該方法可以確保節(jié)點是線程安全添加的帮毁。在enq(Node node)方法中实苞,AQS通過“死循環(huán)”的方式來保證節(jié)點可以正確添加,只有成功添加后烈疚,當前線程才會從該方法返回黔牵,否則會一直執(zhí)行下去。
過程圖如下:
出列
??CLH同步隊列遵循FIFO爷肝,首節(jié)點的線程釋放同步狀態(tài)后猾浦,將會喚醒它的后繼節(jié)點(next),而后繼節(jié)點將會在獲取同步狀態(tài)成功時將自己設(shè)置為首節(jié)點灯抛,這個過程非常簡單金赦,head執(zhí)行該節(jié)點并斷開原首節(jié)點的next和當前節(jié)點的prev即可,注意在這個過程是不需要使用CAS來保證的对嚼,因為只有一個線程能夠成功獲取到同步狀態(tài)夹抗。
過程圖如下:
同步狀態(tài)的獲取與釋放
??在前面提到過,AQS是構(gòu)建Java同步組件的基礎(chǔ)纵竖,我們期待它能夠成為實現(xiàn)大部分同步需求的基礎(chǔ)漠烧。AQS的設(shè)計模式采用的模板方法模式,子類通過繼承的方式磨确,實現(xiàn)它的抽象方法來管理同步狀態(tài)沽甥,對于子類而言它并沒有太多的活要做,AQS提供了大量的模板方法來實現(xiàn)同步乏奥,主要是分為三類:獨占式獲取和釋放同步狀態(tài)摆舟、共享式獲取和釋放同步狀態(tài)、查詢同步隊列中的等待線程情況。自定義子類使用AQS提供的模板方法就可以實現(xiàn)自己的同步語義恨诱。
獨占式:同一時刻僅有一個線程持有同步狀態(tài)媳瞪。
獨占式同步狀態(tài)獲取:
??acquire(int arg)方法為AQS提供的模板方法照宝,該方法為獨占式獲取同步狀態(tài)蛇受,但是該方法對中斷不敏感,也就是說由于線程獲取同步狀態(tài)失敗加入到CLH同步隊列中厕鹃,后續(xù)對線程進行中斷操作時兢仰,線程不會從同步隊列中移除。代碼如下:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
各個方法定義如下:
- tryAcquire:去嘗試獲取鎖剂碴,獲取成功則設(shè)置鎖狀態(tài)并返回true把将,否則返回false。該方法自定義同步組件自己實現(xiàn)忆矛,該方法必須要保證線程安全的獲取同步狀態(tài)察蹲。
- addWaiter:如果tryAcquire返回FALSE(獲取同步狀態(tài)失敗)催训,則調(diào)用該方法將當前線程加入到CLH同步隊列尾部洽议。
- acquireQueued:當前線程會根據(jù)公平性原則來進行阻塞等待(自旋),直到獲取鎖為止;并且返回當前線程在等待過程中有沒有中斷過漫拭。
- selfInterrupt:產(chǎn)生一個中斷亚兄。
acquireQueued方法為一個自旋的過程,也就是說當前線程(Node)進入同步隊列后嫂侍,就會進入一個自旋的過程儿捧,每個節(jié)點都會自省地觀察,當條件滿足挑宠,獲取到同步狀態(tài)后,就可以從這個自旋過程中退出颓影,否則會一直執(zhí)行下去各淀。
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true;
try {
//中斷標志
boolean interrupted = false;
/*
* 自旋過程,其實就是一個死循環(huán)而已
*/
for (;;) {
//當前線程的前驅(qū)節(jié)點
final Node p = node.predecessor();
//當前線程的前驅(qū)節(jié)點是頭結(jié)點诡挂,且同步狀態(tài)成功
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);
}
}
??從上面代碼中可以看到,當前線程會一直嘗試獲取同步狀態(tài)璃俗,當然前提是只有其前驅(qū)節(jié)點為頭結(jié)點才能夠嘗試獲取同步狀態(tài)奴璃,理由:
- 保持FIFO同步隊列原則。
- 頭節(jié)點釋放同步狀態(tài)后城豁,將會喚醒其后繼節(jié)點苟穆,后繼節(jié)點被喚醒后需要檢查自己是否為頭節(jié)點。
acquire(int arg)方法流程圖如下:
獨占式獲取響應(yīng)中斷
??AQS提供了acquire(int arg)方法以供獨占式獲取同步狀態(tài),但是該方法對中斷不響應(yīng)雳旅,對線程進行中斷操作后跟磨,該線程會依然位于CLH同步隊列中等待著獲取同步狀態(tài)。為了響應(yīng)中斷攒盈,AQS提供了acquireInterruptibly(int arg)方法抵拘,該方法在等待獲取同步狀態(tài)時,如果當前線程被中斷了型豁,會立刻響應(yīng)中斷拋出異常InterruptedException僵蛛。
public final void acquireInterruptibly(int arg)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
if (!tryAcquire(arg))
doAcquireInterruptibly(arg);
}
??首先校驗該線程是否已經(jīng)中斷了,如果是則拋出InterruptedException迎变,否則執(zhí)行tryAcquire(int arg)方法獲取同步狀態(tài)充尉,如果獲取成功,則直接返回氏豌,否則執(zhí)行doAcquireInterruptibly(int arg)喉酌。doAcquireInterruptibly(int arg)定義如下:
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())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
??doAcquireInterruptibly(int arg)方法與acquire(int arg)方法僅有兩個差別。
- 方法聲明拋出InterruptedException異常泵喘。
- 在中斷方法處不再是使用interrupted標志泪电,而是直接拋出InterruptedException異常。
獨占式超時獲取
??AQS除了提供上面兩個方法外纪铺,還提供了一個增強版的方法:tryAcquireNanos(int arg,long nanos)相速。該方法為acquireInterruptibly方法的進一步增強,它除了響應(yīng)中斷外鲜锚,還有超時控制突诬。即如果當前線程沒有在指定時間內(nèi)獲取同步狀態(tài),則會返回false芜繁,否則返回true旺隙。如下:
public final boolean tryAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException();
return tryAcquire(arg) ||
doAcquireNanos(arg, nanosTimeout);
}
tryAcquireNanos(int arg, long nanosTimeout)方法超時獲取最終是在doAcquireNanos(int arg, long nanosTimeout)中實現(xiàn)的,如下:
private boolean doAcquireNanos(int arg, long nanosTimeout)
throws InterruptedException {
//nanosTimeout <= 0
if (nanosTimeout <= 0L)
return false;
//超時時間
final long deadline = System.nanoTime() + nanosTimeout;
//新增Node節(jié)點
final Node node = addWaiter(Node.EXCLUSIVE);
boolean failed = true;
try {
//自旋
for (;;) {
final Node p = node.predecessor();
//獲取同步狀態(tài)成功
if (p == head && tryAcquire(arg)) {
setHead(node);
p.next = null; // help GC
failed = false;
return true;
}
/*
* 獲取失敗骏令,做超時蔬捷、中斷判斷
*/
//重新計算需要休眠的時間
nanosTimeout = deadline - System.nanoTime();
//已經(jīng)超時,返回false
if (nanosTimeout <= 0L)
return false;
//如果沒有超時榔袋,則等待nanosTimeout納秒
//注:該線程會直接從LockSupport.parkNanos中返回周拐,
//LockSupport為JUC提供的一個阻塞和喚醒的工具類,后面做詳細介紹
if (shouldParkAfterFailedAcquire(p, node) &&
nanosTimeout > spinForTimeoutThreshold)
LockSupport.parkNanos(this, nanosTimeout);
//線程是否已經(jīng)中斷了
if (Thread.interrupted())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
??針對超時控制凰兑,程序首先記錄喚醒時間deadline 妥粟,deadline = System.nanoTime() + nanosTimeout(時間間隔)。如果獲取同步狀態(tài)失敗吏够,則需要計算出需要休眠的時間間隔nanosTimeout(= deadline - System.nanoTime())勾给,如果nanosTimeout <= 0 表示已經(jīng)超時了滩报,返回false,如果大于spinForTimeoutThreshold(1000L)則需要休眠nanosTimeout 锦秒,如果nanosTimeout <= spinForTimeoutThreshold 露泊,就不需要休眠了,直接進入快速自旋的過程旅择。原因在于 spinForTimeoutThreshold 已經(jīng)非常小了惭笑,非常短的時間等待無法做到十分精確,如果這時再次進行超時等待生真,相反會讓nanosTimeout 的超時從整體上面表現(xiàn)得不是那么精確沉噩,所以在超時非常短的場景中,AQS會進行無條件的快速自旋柱蟀。
獨占式同步狀態(tài)釋放
??當線程獲取同步狀態(tài)后川蒙,執(zhí)行完相應(yīng)邏輯后就需要釋放同步狀態(tài)。AQS提供了release(int arg)方法釋放同步狀態(tài):
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
??該方法同樣是先調(diào)用自定義同步器自定義的tryRelease(int arg)方法來釋放同步狀態(tài)长已,釋放成功后畜眨,會調(diào)用unparkSuccessor(Node node)方法喚醒后繼節(jié)點(如何喚醒LZ后面介紹)。
這里稍微總結(jié)下:
在AQS中維護著一個FIFO的同步隊列术瓮,當線程獲取同步狀態(tài)失敗后康聂,則會加入到這個CLH同步隊列的對尾并一直保持著自旋。在CLH同步隊列中的線程在自旋時會判斷其前驅(qū)節(jié)點是否為首節(jié)點胞四,如果為首節(jié)點則不斷嘗試獲取同步狀態(tài)恬汁,獲取成功則退出CLH同步隊列。當線程執(zhí)行完邏輯后辜伟,會釋放同步狀態(tài)氓侧,釋放后會喚醒其后繼節(jié)點。
共享式
共享式與獨占式的最主要區(qū)別在于同一時刻獨占式只能有一個線程獲取同步狀態(tài)导狡,而共享式在同一時刻可以有多個線程獲取同步狀態(tài)约巷。例如讀操作可以有多個線程同時進行,而寫操作同一時刻只能有一個線程進行寫操作旱捧,其他操作都會被阻塞载庭。
共享式同步狀態(tài)獲取
??AQS提供acquireShared(int arg)方法共享式獲取同步狀態(tài):
public final void acquireShared(int arg) {
if (tryAcquireShared(arg) < 0)
//獲取失敗,自旋獲取同步狀態(tài)
doAcquireShared(arg);
}
??從上面程序可以看出廊佩,方法首先是調(diào)用tryAcquireShared(int arg)方法嘗試獲取同步狀態(tài),如果獲取失敗則調(diào)用doAcquireShared(int arg)自旋方式獲取同步狀態(tài)靖榕,共享式獲取同步狀態(tài)的標志是返回 >= 0 的值表示獲取成功标锄。自選式獲取同步狀態(tài)如下:
private void doAcquireShared(int arg) {
/共享式節(jié)點
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
boolean interrupted = false;
for (;;) {
//前驅(qū)節(jié)點
final Node p = node.predecessor();
//如果其前驅(qū)節(jié)點,獲取同步狀態(tài)
if (p == head) {
//嘗試獲取同步
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
if (interrupted)
selfInterrupt();
failed = false;
return;
}
}
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
}
} finally {
if (failed)
cancelAcquire(node);
}
}
??tryAcquireShared(int arg)方法嘗試獲取同步狀態(tài)茁计,返回值為int料皇,當其 >= 0 時谓松,表示能夠獲取到同步狀態(tài),這個時候就可以從自旋過程中退出践剂。
??acquireShared(int arg)方法不響應(yīng)中斷鬼譬,與獨占式相似,AQS也提供了響應(yīng)中斷逊脯、超時的方法优质,分別是:acquireSharedInterruptibly(int arg)、tryAcquireSharedNanos(int arg,long nanos)军洼,這里就不做解釋了巩螃。
共享式同步狀態(tài)釋放
??獲取同步狀態(tài)后,需要調(diào)用release(int arg)方法釋放同步狀態(tài)匕争,方法如下:
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
因為可能會存在多個線程同時進行釋放同步狀態(tài)資源避乏,所以需要確保同步狀態(tài)安全地成功釋放,一般都是通過CAS和循環(huán)來完成的甘桑。
阻塞和喚醒線程
??在線程獲取同步狀態(tài)時如果獲取失敗拍皮,則加入CLH同步隊列,通過通過自旋的方式不斷獲取同步狀態(tài)跑杭,但是在自旋的過程中則需要判斷當前線程是否需要阻塞铆帽,其主要方法在acquireQueued():
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
interrupted = true;
??通過這段代碼我們可以看到,在獲取同步狀態(tài)失敗后艘蹋,線程并不是立馬進行阻塞锄贼,需要檢查該線程的狀態(tài),檢查狀態(tài)的方法為 shouldParkAfterFailedAcquire(Node pred, Node node) 方法女阀,該方法主要靠前驅(qū)節(jié)點判斷當前線程是否應(yīng)該被阻塞宅荤,代碼如下:
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
//前驅(qū)節(jié)點
int ws = pred.waitStatus;
//狀態(tài)為signal,表示當前線程處于等待狀態(tài)浸策,直接放回true
if (ws == Node.SIGNAL)
return true;
//前驅(qū)節(jié)點狀態(tài) > 0 冯键,則為Cancelled,表明該節(jié)點已經(jīng)超時或者被中斷了,需要從同步隊列中取消
if (ws > 0) {
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
}
//前驅(qū)節(jié)點狀態(tài)為Condition庸汗、propagate
else {
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
這段代碼主要檢查當前線程是否需要被阻塞惫确,具體規(guī)則如下:
- 如果當前線程的前驅(qū)節(jié)點狀態(tài)為SINNAL,則表明當前線程需要被阻塞蚯舱,調(diào)用unpark()方法喚醒改化,直接返回true,當前線程阻塞
- 如果當前線程的前驅(qū)節(jié)點狀態(tài)為CANCELLED(ws > 0)枉昏,則表明該線程的前驅(qū)節(jié)點已經(jīng)等待超時或者被中斷了陈肛,則需要從CLH隊列中將該前驅(qū)節(jié)點刪除掉,直到回溯到前驅(qū)節(jié)點狀態(tài) <= 0 兄裂,返回false
- 如果前驅(qū)節(jié)點非SINNAL句旱,非CANCELLED阳藻,則通過CAS的方式將其前驅(qū)節(jié)點設(shè)置為SINNAL,返回false
如果 shouldParkAfterFailedAcquire(Node pred, Node node) 方法返回true谈撒,則調(diào)用parkAndCheckInterrupt()方法阻塞當前線程:
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
parkAndCheckInterrupt() 方法主要是把當前線程掛起腥泥,從而阻塞住線程的調(diào)用棧,同時返回當前線程的中斷狀態(tài)啃匿。其內(nèi)部則是調(diào)用LockSupport工具類的park()方法來阻塞該方法蛔外。
當線程釋放同步狀態(tài)后,則需要喚醒該線程的后繼節(jié)點:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
//喚醒后繼節(jié)點
unparkSuccessor(h);
return true;
}
return false;
}
調(diào)用unparkSuccessor(Node node)喚醒后繼節(jié)點:
private void unparkSuccessor(Node node) {
//當前節(jié)點狀態(tài)
int ws = node.waitStatus;
//當前狀態(tài) < 0 則設(shè)置為 0
if (ws < 0)
compareAndSetWaitStatus(node, ws, 0);
//當前節(jié)點的后繼節(jié)點
Node s = node.next;
//后繼節(jié)點為null或者其狀態(tài) > 0 (超時或者被中斷了)
if (s == null || s.waitStatus > 0) {
s = null;
//從tail節(jié)點來找可用節(jié)點
for (Node t = tail; t != null && t != node; t = t.prev)
if (t.waitStatus <= 0)
s = t;
}
//喚醒后繼節(jié)點
if (s != null)
LockSupport.unpark(s.thread);
}
可能會存在當前線程的后繼節(jié)點為null立宜,超時冒萄、被中斷的情況,如果遇到這種情況了橙数,則需要跳過該節(jié)點尊流,但是為何是從tail尾節(jié)點開始,而不是從node.next開始呢灯帮?原因在于node.next仍然可能會存在null或者取消了崖技,所以采用tail回溯辦法找第一個可用的線程。最后調(diào)用LockSupport的unpark(Thread thread)方法喚醒該線程钟哥。
LockSupport
從上面我可以看到迎献,當需要阻塞或者喚醒一個線程的時候,AQS都是使用LockSupport這個工具類來完成的腻贰。
LockSupport是用來創(chuàng)建鎖和其他同步類的基本線程阻塞原語
每個使用LockSupport的線程都會與一個許可關(guān)聯(lián)吁恍,如果該許可可用,并且可在進程中使用播演,則調(diào)用park()將會立即返回冀瓦,否則可能阻塞。如果許可尚不可用写烤,則可以調(diào)用 unpark 使其可用翼闽。但是注意許可不可重入,也就是說只能調(diào)用一次park()方法洲炊,否則會一直阻塞感局。
LockSupport定義了一系列以park開頭的方法來阻塞當前線程,unpark(Thread thread)方法來喚醒一個被阻塞的線程暂衡。如下:
park(Object blocker)方法的blocker參數(shù)询微,主要是用來標識當前線程在等待的對象,該對象主要用于問題排查和系統(tǒng)監(jiān)控狂巢。
park方法和unpark(Thread thread)都是成對出現(xiàn)的拓提,同時unpark必須要在park執(zhí)行之后執(zhí)行,當然并不是說沒有不調(diào)用unpark線程就會一直阻塞隧膘,park有一個方法代态,它帶了時間戳(parkNanos(long nanos):為了線程調(diào)度禁用當前線程,最多等待指定的等待時間疹吃,除非許可可用)蹦疑。
park()方法的源碼如下:
public static void park() {
UNSAFE.park(false, 0L);
}
unpark(Thread thread)方法源碼如下:
public static void unpark(Thread thread) {
if (thread != null)
UNSAFE.unpark(thread);
}
從上面可以看出,其內(nèi)部的實現(xiàn)都是通過UNSAFE(sun.misc.Unsafe UNSAFE)來實現(xiàn)的萨驶,其定義如下:
public native void park(boolean var1, long var2);
public native void unpark(Object var1);
兩個都是native本地方法歉摧。Unsafe 是一個比較危險的類,主要是用于執(zhí)行低級別腔呜、不安全的方法集合叁温。盡管這個類和所有的方法都是公開的(public),但是這個類的使用仍然受限核畴,你無法在自己的java程序中直接使用該類膝但,因為只有授信的代碼才能獲得該類的實例锋八。