解釋
AQS:全稱(chēng)“AbstractQueuedSynchronizer”,直譯過(guò)來(lái)是抽象的隊(duì)列同步器边苹,一般我們把它叫做AQS,java中大部分并發(fā)類(lèi)都是通過(guò)它來(lái)實(shí)現(xiàn)線(xiàn)程同步裁僧。它內(nèi)部定義了一個(gè)變量(volatile int state)和一個(gè)等待隊(duì)列勾给,前者表示加鎖狀態(tài),后者在多線(xiàn)程情況下?tīng)?zhēng)用資源時(shí)被阻塞會(huì)進(jìn)入等待隊(duì)列锅知。
源碼解析
volatile int state
volatile 是一個(gè)關(guān)鍵字播急,在并發(fā)處理中也經(jīng)常會(huì)用到,后面單獨(dú)用一篇文章介紹售睹。
(已去除源碼中的注釋?zhuān)奖汩喿x)
private volatile int state;
protected final int getState() {
return state;
}
protected final void setState(int newState) {
state = newState;
}
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}
獨(dú)占鎖(下面會(huì)介紹)state初始為0桩警,表示未鎖定狀態(tài),A線(xiàn)程加鎖成功則state+1昌妹,這樣后面的線(xiàn)程嘗試獲取鎖的時(shí)候就會(huì)失敗捶枢,只有當(dāng)A線(xiàn)程釋放鎖state=0時(shí),后面的鎖才有可能成功獲取鎖飞崖。如果是可重入鎖烂叔,那么A線(xiàn)程再次獲取鎖的時(shí)候,state會(huì)累加固歪,當(dāng)然蒜鸡,釋放鎖也要一層一層釋放,直到state=0牢裳。共享鎖state初始為N逢防,多個(gè)線(xiàn)程可以同時(shí)執(zhí)行,每個(gè)線(xiàn)程執(zhí)行完會(huì)state-1蒲讯,直到state=0時(shí)忘朝,再調(diào)用主線(xiàn)程。
其中compareAndSetState方法判帮,是原子操作局嘁,里面使用了unsafe中的compareAndSwapInt方法溉箕,這個(gè)Unsafe(不安全)類(lèi),聽(tīng)著就不靠譜悦昵,為什么這里會(huì)用到呢约巷。其實(shí)不止在這里,在JUC(java.util.concurrent)包中旱捧,尤其是在CAS里大量的用到Unsafe独郎,這是因?yàn)镴ava不能像c語(yǔ)言那樣直接訪(fǎng)問(wèn)操作系統(tǒng)底層,但Unsafe類(lèi)提供了硬件級(jí)別的原子操作枚赡,而且有很高的效率氓癌。那為什么官方不建議開(kāi)發(fā)者使用這個(gè)類(lèi)呢,感覺(jué)特地取個(gè)這名字(Unsafe)就是嚇唬程序員贫橙,不要用贪婉。因?yàn)閁nsafe中直接訪(fǎng)問(wèn)內(nèi)存的方法中使用的內(nèi)存不受JVM管理,也就不能被GC卢肃,需要手動(dòng)管理疲迂,稍有不慎就可能導(dǎo)致內(nèi)存泄漏。
隊(duì)列操作
private Node enq(final Node node) {
for (;;) {
Node t = tail;
if (t == null) { // Must initialize
if (compareAndSetHead(new Node()))
tail = head;
} else {
node.prev = t;
if (compareAndSetTail(t, node)) {
t.next = node;
return t;
}
}
}
}
將節(jié)點(diǎn)插入隊(duì)列莫湘,必要時(shí)進(jìn)行初始化尤蒿。
里面的for (;;),又是個(gè)騷操作幅垮,有人會(huì)問(wèn)為啥不用while(true)呢腰池,都是無(wú)限循環(huán)忙芒,而且while(true)看著就很舒服示弓。經(jīng)常看源碼的同學(xué)呵萨,可能會(huì)發(fā)現(xiàn),jdk源碼中很多地方都是for(;;)囱皿,至于為啥用這個(gè)咆耿,咱們以后有機(jī)會(huì)再討論萨螺。
Node t = tail;(tail是尾節(jié)點(diǎn),head是頭節(jié)點(diǎn))先定義一個(gè)尾部節(jié)點(diǎn)椭盏,如果尾節(jié)點(diǎn)是空的掏颊,說(shuō)明隊(duì)列是空的,這時(shí)就需要初始化盆偿,源碼中特地加了注釋// Must initialize事扭,(這種直接在后面加注釋的乐横,咱們最好不要學(xué)罐农,不符合阿里代碼規(guī)范蛆楞,但是Doug Lea是大大佬豹爹,阿里代碼規(guī)范管不住他);初始化以后,接著循環(huán)進(jìn)入else艾君,node.prev = t;將當(dāng)前節(jié)點(diǎn)掛在尾節(jié)點(diǎn)后面冰垄;(這種node.prev指向上個(gè)節(jié)點(diǎn)逝薪,t.next指向下個(gè)節(jié)點(diǎn),這是雙向隊(duì)列的操作,還有單向隊(duì)列询微,有興趣的可以去了解一下)撑毛。
看這一個(gè)方法里就出現(xiàn)了compareAndSetHead和compareAndSetTail兩個(gè)CAS操作,點(diǎn)進(jìn)去發(fā)現(xiàn)還是Unsafe實(shí)現(xiàn)的,由此可見(jiàn)這個(gè)類(lèi)在多線(xiàn)程中還是很重要的。
AQS中還有很多方法居暖,咱們不一一介紹了嘁圈,下面介紹幾個(gè)和資源共享相關(guān)的方法钞澳。
資源共享方式
AQS定義了兩種資源共享方式
- 獨(dú)占資源,只有一個(gè)線(xiàn)程能執(zhí)行轧拄,如ReentrantLock
- 共享資源拄丰,多個(gè)線(xiàn)程可以同時(shí)執(zhí)行料按,如Semaphore/CountDownLatch烹卒。
獨(dú)占模式采用tryAcquire-tryRelease實(shí)現(xiàn),共享模式采用tryAcquireShared-tryReleaseShared實(shí)現(xiàn)溺拱。有一些特殊的鎖兩種模式都使用迫摔,比如ReentrantReadWriteLock(讀寫(xiě)鎖)攒菠。
-
isHeldExclusively():該線(xiàn)程是否獨(dú)占資源。
protected boolean isHeldExclusively() { throw new UnsupportedOperationException(); }
-
tryAcquire(int):獨(dú)占方式凹炸。嘗試獲取資源昼弟。
protected boolean tryAcquire(int arg) { throw new UnsupportedOperationException(); }
-
tryRelease(int):獨(dú)占方式塌碌。嘗試釋放資源。
protected boolean tryRelease(int arg) { throw new UnsupportedOperationException(); }
-
tryAcquireShared(int):共享方式接剩。嘗試獲取資源遗座。負(fù)數(shù)表示失斣逼肌; 0 表示成功日麸,但沒(méi)有剩余
可用資源;正數(shù)表示成功涕刚,且有剩余資源。protected int tryAcquireShared(int arg) { throw new UnsupportedOperationException(); }
-
tryReleaseShared(int):共享方式晨缴。嘗試釋放資源睛竣,如果釋放后允許喚醒后續(xù)等待結(jié)點(diǎn)返回
true验夯,否則返回 false。protected boolean tryReleaseShared(int arg) { throw new UnsupportedOperationException(); }