在Java中,如果要對(duì)某一共享資源進(jìn)行互斥訪問(wèn),一般可以通過(guò)使用synchronized關(guān)鍵字或者是java.util.concurrent.locks包下面的某些類來(lái)實(shí)現(xiàn),比如ReentrantLock。這個(gè)包下的所有類幾乎都是以AbstractQueuedSynchronizer(后文中簡(jiǎn)稱AQS)為基礎(chǔ)來(lái)構(gòu)建的,AQS為鎖的實(shí)現(xiàn)提供了一個(gè)基本機(jī)制,包括掛起和喚醒某一線程、自動(dòng)管理同步狀態(tài)以及將獲取不到鎖的線程入隊(duì)等,在這些基礎(chǔ)機(jī)制之上,可以實(shí)現(xiàn)各種特性不一樣的鎖,比如公平鎖,非公平鎖,可被中斷的鎖等等
在基本用法上,synchronized和ReentrantLock很相似,基本用法都差不多,語(yǔ)義也差不多,都具備可重入等特性。synchronized是在字節(jié)碼層面上通過(guò)monitorenter和monitorexit來(lái)實(shí)現(xiàn)的,線程的阻塞(獲取不到鎖時(shí))和喚醒(待獲取的鎖被另一擁有它的線程釋放時(shí))是內(nèi)核自己調(diào)度的,而基于AQS的鎖實(shí)現(xiàn)可以讓實(shí)現(xiàn)者自己決定線程獲取鎖的規(guī)則(當(dāng)某一線程釋放鎖,n多之前因?yàn)闆](méi)有獲取到鎖而阻塞的線程去獲取這個(gè)已經(jīng)釋放的鎖時(shí)),比如公平鎖和非公平鎖,相比于synchronized,ReentrantLock有比較多的高級(jí)特性,有業(yè)務(wù)需求的話也可以通過(guò)實(shí)現(xiàn)AQS來(lái)進(jìn)行擴(kuò)展,可擴(kuò)展性較好
分析AQS之前需要知道CAS(即compare-and-swap,比較并交換)。某些硬件的指令集提供了CAS指令,CAS指令需要三個(gè)操作數(shù),分別是內(nèi)存地址Loc,舊的預(yù)期值oldValue,新的更新值updateValue,CAS指令執(zhí)行時(shí),當(dāng)且僅當(dāng)oldValue等于Loc位置處的值時(shí),將Loc處的值更新為updateValue,否則不更新。這個(gè)CAS操作是一個(gè)原子操作,且比synchronized更輕量。
如果不想使用synchronized來(lái)更新共享變量的值(考慮到性能),可以用CAS+volatile的方式來(lái)保證線程安全,CAS操作保證了原子性,而volatile保證了可見(jiàn)性
AQS里面會(huì)用到一個(gè)比較重要的類,sun.misc.Unsafe,這個(gè)類的大部分方法都是native方法,可以執(zhí)行一些比較“另類”的方法,例如向操作系統(tǒng)申請(qǐng)內(nèi)存,也可以用Unsafe來(lái)執(zhí)行CAS操作,掛起和喚醒線程也是通過(guò)Unsafe的park和unpark來(lái)實(shí)現(xiàn)的
public abstract class AbstractQueuedSynchronizer
extends AbstractOwnableSynchronizer
implements java.io.Serializable {
private transient volatile Node head; //鏈表的頭指針
private transient volatile Node tail; //鏈表的尾指針
private volatile int state; ///同步狀態(tài)
static final class Node {
static final Node SHARED = new Node(); //當(dāng)前等待的鎖為共享模式
static final Node EXCLUSIVE = null; //當(dāng)前等待的鎖為排他模式
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;
}
}
可以看到AQS里面有一個(gè)內(nèi)部類Node,Node里面有prev和next引用,因此Node可以構(gòu)成一個(gè)雙向鏈表,并且Node里面還有一個(gè)Thread的引用,可以猜想,每一個(gè)打算去獲取鎖的線程在AQS里面都會(huì)先包裝成一個(gè)Node,假如獲取不到鎖的話,會(huì)將當(dāng)前的Node加入這個(gè)鏈表,當(dāng)某一個(gè)線程釋放鎖的時(shí)候,可以從鏈表里面取一個(gè)正在等待的Node,并將鎖給他
首先來(lái)看看獲取鎖的方法
public final void acquire(int arg){
if(!tryAcquire(arg)&&
acquireQueued(addWaiter(Node.EXCLUSIVE),arg))
selfInterrupt();
}
整個(gè)方法的邏輯為:首先嘗試去獲取鎖,如果由于競(jìng)爭(zhēng)失敗導(dǎo)致獲取鎖失敗,則將自身包裝成一個(gè)node對(duì)象加入到鏈表末尾,并嘗試去獲取鎖
其中tryAcquire(arg)為一個(gè)protected方法,需要子類去實(shí)現(xiàn),這個(gè)方法試圖去獲取鎖,如果成功獲取到鎖,則返回true,否則返回false。等下次分析某個(gè)AQS的實(shí)現(xiàn)類時(shí),可以看到tryAcquire(arg)的一些具體實(shí)現(xiàn)(會(huì)涉及到同步狀態(tài)state),但是現(xiàn)在可以不用管它,只需要知道,如果獲取到鎖了就返回true,沒(méi)獲取到鎖就返回false
如果沒(méi)有獲取到鎖,則進(jìn)入addWaiter(Node mode)方法
本文中,說(shuō)到步驟的時(shí)候都是以 “方法名.步驟n” 這樣一種形式來(lái)描述,以避免沖突,假如方法是當(dāng)前正在分析的方法,則省略方法名,只說(shuō)步驟n
private Node addWaiter(Node mode) {
Node node = new Node(Thread.currentThread(), mode): //1
// Try the fast path of enq; backup to full enq on failure
Node pred = tail;
if (pred != null) { //2
node.prev = pred;
if (compareAndSetTail(pred, node)) { //3
pred.next = node;
return node;
}
}
enq(node); //4
return node; //5
}
這個(gè)方法的步驟為:
1:首先利用當(dāng)前線程創(chuàng)建node節(jié)點(diǎn),并取出尾節(jié)點(diǎn)tail
2:判斷之前的尾節(jié)點(diǎn)tail是否為null。如果不為null,則將新創(chuàng)建節(jié)點(diǎn)node的prev引用指向尾節(jié)點(diǎn)tail,并進(jìn)入步驟3。如果尾節(jié)點(diǎn)為null,則進(jìn)入步驟4
3:這時(shí)候利用CAS操作試圖將尾節(jié)點(diǎn)修改為新創(chuàng)建的node節(jié)點(diǎn)。如果CAS操作成功,則新創(chuàng)建的節(jié)點(diǎn)成為尾節(jié)點(diǎn),并且將之前尾節(jié)點(diǎn)的next引用指向新創(chuàng)建的節(jié)點(diǎn)node,實(shí)現(xiàn)一個(gè)鏈表中兩個(gè)節(jié)點(diǎn)的連接行為,并返回這個(gè)新創(chuàng)建的節(jié)點(diǎn)node。如果CAS操作失敗,則說(shuō)明已經(jīng)有其它線程搶先一步將它自己加入到了鏈表中,此時(shí),進(jìn)入步驟4
4:如果尾節(jié)點(diǎn)為null或者步驟3中的CAS操作失敗,則會(huì)進(jìn)入此步驟,注意步驟4中傳入的參數(shù)為新創(chuàng)建的node節(jié)點(diǎn)
接下來(lái)看下步驟4中的enq(Node node)方法,注意,enq方法是用final修飾的
private Node enq(final Node node) {
for (; ; ) {
Node t = tail; //1
if (t == null) { // Must initialize
if (compareAndSetHead(new Node())) //2
tail = head;
} else {
node.prev = t; //3
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
此方法中,for是一個(gè)無(wú)線循環(huán),出口只有else分支里面的一個(gè)return語(yǔ)句,此方法的步驟為:
1:首先取出尾節(jié)點(diǎn)tail,并判斷尾節(jié)點(diǎn)是否為null。如果尾節(jié)點(diǎn)為null,則進(jìn)入步驟2,否則進(jìn)入步驟3
2:新創(chuàng)建一個(gè)節(jié)點(diǎn)(這個(gè)節(jié)點(diǎn)并沒(méi)有綁定某一個(gè)線程,可以認(rèn)為是一個(gè)“空”節(jié)點(diǎn)),并通過(guò)CAS操作嘗試將其設(shè)置為頭結(jié)點(diǎn),如果設(shè)置成功,將尾節(jié)點(diǎn)也指向剛創(chuàng)建的節(jié)點(diǎn),并進(jìn)入下一次循環(huán),此時(shí)tail節(jié)點(diǎn)已經(jīng)不為null。需要注意的是這里并沒(méi)有采用CAS的方式將新創(chuàng)建的節(jié)點(diǎn)設(shè)置為tail,這是不必要的,因?yàn)閞eturn語(yǔ)句在else分支里面,而進(jìn)入else分支的條件是尾節(jié)點(diǎn)不為null,假如我們?cè)趖ail = head處打一個(gè)斷點(diǎn),并且讓當(dāng)前線程跑到這個(gè)斷點(diǎn)處,這時(shí)候讓其它的線程調(diào)用acquire方法,當(dāng)其他的線程進(jìn)入enq方法后,由于頭結(jié)點(diǎn)已經(jīng)被設(shè)置了,所以其他線程在//2處的CAS操作將失敗,此時(shí)會(huì)進(jìn)行下一次循環(huán),從這可以看到,初始化的時(shí)候,假如A線程設(shè)置了head節(jié)點(diǎn),那就只有A線程有權(quán)利更新尾節(jié)點(diǎn)tail,之后方法才能正常進(jìn)行下去。而只有設(shè)置了tail節(jié)點(diǎn)之后,代碼才有可能走到else分支從而返回。
3:如果尾節(jié)點(diǎn)tail存在,則將傳入?yún)?shù)node的prev指針指向tail,并利用CAS操作嘗試將傳入的節(jié)點(diǎn)node設(shè)置為尾節(jié)點(diǎn),如果CAS操作成功,則將之前尾節(jié)點(diǎn)的next指針指向傳入的參數(shù)node,并返回之前的尾節(jié)點(diǎn)tail,如果CAS操作失敗(其它線程搶先一步將其自己設(shè)置成了尾節(jié)點(diǎn)),則進(jìn)入下一次循環(huán)并繼續(xù)嘗試將傳入的參數(shù)節(jié)點(diǎn)node設(shè)置為尾節(jié)點(diǎn),直到CAS操作在某次重試中成功
當(dāng)enq(Node node)方法成功返回后,進(jìn)入步驟addWaiter.5,返回新創(chuàng)建的node
所以addWaiter()要做的就是,在多線程的環(huán)境下,競(jìng)爭(zhēng)鎖失敗的時(shí)候,將當(dāng)前線程包裝成一個(gè)node節(jié)點(diǎn),并將其安全地插入到鏈表的末尾。
需要注意的是,假如之前一個(gè)線程已經(jīng)獲得了某一個(gè)鎖且未釋放,也沒(méi)有其它線程請(qǐng)求去獲取鎖,這時(shí)候鏈表中并沒(méi)有任何節(jié)點(diǎn)元素,這時(shí)候假如有另一個(gè)線程去請(qǐng)求獲取這個(gè)鎖,則會(huì)將當(dāng)前線程封裝的節(jié)點(diǎn)node插入鏈表末尾,這個(gè)節(jié)點(diǎn)在鏈表中的位置并不是頭結(jié)點(diǎn),而是鏈表中的第二個(gè)節(jié)點(diǎn)。初始化鏈表的時(shí)候會(huì)插入一個(gè)不綁定線程的“空”節(jié)點(diǎn)
接下來(lái)調(diào)用acquireQueued方法
acquireQueued(final Node node, int arg)方法如下所示,可以看到,只有在當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)是頭結(jié)點(diǎn)head的時(shí)候才有可能獲取到鎖(即當(dāng)前節(jié)點(diǎn)的前一個(gè)節(jié)點(diǎn)為head頭結(jié)點(diǎn)是當(dāng)前節(jié)點(diǎn)能獲取到鎖的前提條件,考慮到非公平鎖,這么說(shuō)其實(shí)有點(diǎn)不太正確)
final boolean acquireQueued(final Node node, int arg) {
boolean failed = true; //1
try {
boolean interrupted = false;
for (; ; ) {
final Node p = node.predecessor();
if (p == head && tryAcquire(arg)) { //2
setHead(node);
p.next = null; // help GC
failed = false;
return interrupted;
}
if (shouldParkAfterFailedAcquire(p, node) && //3
parkAndCheckInterrupt())
interrupted = true;
}
} finally { //4
if (failed)
cancelAcquire(node);
}
}
該方法的步驟為:
1:首先將failed初始化為true,表示獲取鎖是否失敗,失敗了會(huì)在步驟4中做一些后續(xù)工作,至于interrupted的含義,會(huì)在后文看到。然后取出當(dāng)前節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)p 。如果p為頭結(jié)點(diǎn)head且當(dāng)前線程能獲取到鎖,則進(jìn)入步驟2。否則,如果不是頭結(jié)點(diǎn)或者獲取不到鎖,進(jìn)入步驟3
2:當(dāng)當(dāng)前節(jié)點(diǎn)node的prev為head頭結(jié)點(diǎn),且能獲取到鎖時(shí),進(jìn)入該步驟。將當(dāng)前節(jié)點(diǎn)設(shè)為頭結(jié)點(diǎn),并且將之前的head頭結(jié)點(diǎn)從鏈表中分離,這時(shí)候?qū)ailed設(shè)置為false,表示成功獲取到了鎖。并且返回interrupted。注意,這里設(shè)置頭結(jié)點(diǎn)head的時(shí)候并不需要通過(guò)CAS操作來(lái)進(jìn)行,因?yàn)闆](méi)有競(jìng)爭(zhēng)
3:如果當(dāng)前節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)不為head頭節(jié)點(diǎn),或者沒(méi)有獲取到鎖,會(huì)進(jìn)入該步驟。該步驟會(huì)調(diào)用shouldParkAfterFailedAcquire(Node pred, Node node)方法,這個(gè)方法的功能已經(jīng)被方法名描述的很形象了,就是判斷鎖獲取失敗之后需不需要將當(dāng)前線程掛起(AQS中通過(guò)park和unpark方法來(lái)掛起和喚醒線程),假如shouldParkAfterFailedAcquire返回true,則調(diào)用parkAndCheckInterrupt方法,如果parkAndCheckInterrupt也返回true,則將interrupted賦值為true
首先看看shouldParkAfterFailedAcquire方法
private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
int ws = pred.waitStatus;
if (ws == Node.SIGNAL) //1
return true;
if (ws > 0) { //2
do {
node.prev = pred = pred.prev;
} while (pred.waitStatus > 0);
pred.next = node;
} else { //3
compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
}
return false;
}
先說(shuō)一下內(nèi)部類Node的waitStatus字段以及CANCELLED、SIGNAL、CONDITION、PROPAGATE等字段,在源碼里有關(guān)于這幾個(gè)字段的簡(jiǎn)短說(shuō)明
/** waitStatus value to indicate thread has cancelled */
static final int CANCELLED = 1;
/** waitStatus value to indicate successor's thread needs unparking */
static final int SIGNAL = -1;
/** waitStatus value to indicate thread is waiting on condition */
static final int CONDITION = -2;
大致意思就是說(shuō),waitStatus表示了當(dāng)前節(jié)點(diǎn)所代表線程所在的狀態(tài),有可能為CANCELLED、SIGNAL、CONDITION、PROPAGATE、0中其中的一種
CANCELLED表示當(dāng)前線程由于超時(shí)或者被中斷
SIGNAL表示當(dāng)前節(jié)點(diǎn)的next指向的節(jié)點(diǎn)正在阻塞或即將被阻塞,所以當(dāng)前節(jié)點(diǎn)釋放鎖或者取消時(shí)要喚醒其后一個(gè)節(jié)點(diǎn)(也就意味著假如當(dāng)前節(jié)點(diǎn)的waitStatus為SIGNAL,當(dāng)它釋放鎖或者取消時(shí)會(huì)去喚醒它的下一個(gè)節(jié)點(diǎn))
CONDITION:表示當(dāng)前節(jié)點(diǎn)處于條件隊(duì)列中
PROPAGATE:表示喚醒當(dāng)前節(jié)點(diǎn)的動(dòng)作需要向后傳播(不僅僅限于當(dāng)前節(jié)點(diǎn)的后一個(gè)節(jié)點(diǎn)),在某些共享鎖里面會(huì)用到這個(gè),排它鎖不會(huì)用到這個(gè)值
1:如果當(dāng)前節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)pred的waitStatus為SIGNAL,則返回true(如果pred節(jié)點(diǎn)的waitStatus為SIGNAL,則意味著當(dāng)它釋放鎖或者取消時(shí)會(huì)去喚醒它的下一個(gè)節(jié)點(diǎn)node,所以這時(shí)候我們只需要將node所代表的線程掛起就行了,所以返回true)
2:如果當(dāng)前節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)的waitStatus大于0,會(huì)將node之前的waitStatus大于0的所有節(jié)點(diǎn)從鏈表中刪除掉,直到某一個(gè)節(jié)點(diǎn)的waitStatus小于等于0,并且將這個(gè)節(jié)點(diǎn)和node互相關(guān)聯(lián)起來(lái)
3:如果當(dāng)前節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)pred的waitStatus等于0,則嘗試用CAS操作將pred節(jié)點(diǎn)的waitStatus值改為Node.SIGNAL
在分支2和分支3中,由于其前面一個(gè)節(jié)點(diǎn)的waitStatus不為SIGNAL,所以沒(méi)辦法保證會(huì)有一個(gè)線程去喚醒node,則只能返回false且進(jìn)入下一次循環(huán)(即只有在前一個(gè)節(jié)點(diǎn)的waitStatus為SIGNAL的情況下,后面的線程才有可能被喚醒,即掛起node所代表的線程是安全的)
如果shouldParkAfterFailedAcquire方法返回true,則進(jìn)入parkAndCheckInterrupt()方法
private final boolean parkAndCheckInterrupt() {
LockSupport.park(this);
return Thread.interrupted();
}
可以看到parkAndCheckInterrupt()方法很簡(jiǎn)單,只是掛起當(dāng)前線程并返回當(dāng)前線程的中斷標(biāo)記(如果有其他線程喚醒它)
如果當(dāng)前線程被設(shè)置過(guò)中斷標(biāo)記(注意,interrupted方法會(huì)清除掉線程的中斷狀態(tài))
則步驟acquireQueued.3中會(huì)將interrupted置為true并返回,以標(biāo)志當(dāng)前線程在等待鎖的過(guò)程中是否被中斷過(guò)
當(dāng)當(dāng)前線程被喚醒后,又會(huì)進(jìn)入下一次循環(huán),繼續(xù)下一次獲取鎖的嘗試,直到其獲取到鎖或者拋出異常
當(dāng)acquireQueued返回true時(shí),acquire方法的selfInterrupt方法會(huì)設(shè)置當(dāng)前線程的中斷標(biāo)記
整個(gè)acquire方法的精髓就在這兒了,一個(gè)節(jié)點(diǎn)node的前一個(gè)節(jié)點(diǎn)假如是head節(jié)點(diǎn),他才會(huì)去嘗試獲取鎖,需要注意的是,這里不一定能夠獲取到鎖(非公平鎖),假如獲取不到鎖,線程會(huì)嘗試將自己掛起,等待被中斷或者被其它的線程喚醒,當(dāng)被喚醒后,會(huì)繼續(xù)嘗試去獲取鎖,直到獲取到鎖或者拋出異常,又或者被取消等等。。
接下來(lái)說(shuō)下線程釋放鎖的過(guò)程:
public final boolean release(int arg) {
if (tryRelease(arg)) {
Node h = head;
if (h != null && h.waitStatus != 0)
unparkSuccessor(h);
return true;
}
return false;
}
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) { //1
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);
}
在unparkSuccessor方法中,如果node的waitStatus為0,首先會(huì)嘗試將node的waitStatus設(shè)置為0,就算CAS操作失敗了也不要緊,這意味著node的waitStatus已經(jīng)被改變了。
在步驟1中,如果當(dāng)前節(jié)點(diǎn)node的后一個(gè)節(jié)點(diǎn)s不為null且s的waitStatus小于等于0,則可以直接喚醒s對(duì)應(yīng)的線程,如果s為null或者s的waitStatus大于0,則會(huì)從鏈表的末尾開(kāi)始向前找,直到找到一個(gè)距離節(jié)點(diǎn)node最近的且waitStatus小于等于0的節(jié)點(diǎn),并喚醒它
Doug Lea大師把CAS指令玩的飛起。只能膜拜!!
待續(xù)...
key words:AQS CAS Unsafe
關(guān)于AQS,多線程大師Doug Lea發(fā)表了一篇論文,《The java.util.concurrent Synchronizer Framework》,感興趣的可以看看