1. 概述
在jdk1.4以前,java的內(nèi)置鎖(使用synchronized的方法或代碼塊)性能問題一直都在被人們關(guān)注.jdk1.5中加入了java.concurrent.util包, 來實(shí)現(xiàn)高性能的同步器(如ReentrantLock, CountDownLatch等).
顯然, java.util.concurrent包的目的有兩個(gè):1.提升性能(相比于synchronized), 2.實(shí)現(xiàn)同步器的基本功能(acquire和release).
那么, java.util.concurrent包是如何達(dá)到以上兩個(gè)目的的呢? 主要從兩方面著手:
1.jdk1.5中提供了硬件級(jí)別的同步原語(compareAndSwap)來提高同步效率;
2.在java層面提供了一個(gè)設(shè)計(jì)良好的抽象同步器AbstractQueuedSync. java.concurrent.util包粽所有的同步器(ReentrantLock,CountDownLatch等)都基于這個(gè)抽象類.
本文將首先介紹compareAndSwap相比于synchronized的優(yōu)勢, 接著介紹一下框架核心AbstractQueuedSync的結(jié)構(gòu), 最后簡述一下java.util.concurrent包中的若干個(gè)同步器.
2. java.concurrent.util包實(shí)現(xiàn)原理
2.1 同步原語(synchronized and CAS)
jdk1.4以前, java用內(nèi)置鎖(synchronized)來實(shí)現(xiàn)同步. 這種方式是非常低效的, 為什么? 來看一下原因.
我們可以通過反編譯原來來看一下synchronized在class文件中的指令. 先編寫一個(gè)類.
package chyun.concurrent;
public class SynchronizedDemo {
public void method() {
synchronized (this) {
System.out.println("Method 1 start");
}
}
}
反編譯結(jié)果:
可以看到synchronized是通過monitorenter和monitorexit兩條指令來實(shí)現(xiàn)的,
關(guān)于這兩條指令的作用焚刚,我們直接參考JVM規(guī)范中描述.
monitorenter:
Each object is associated with a monitor. A monitor is locked if and only if it has an owner. The thread that executes monitorenter attempts to gain ownership of the monitor associated with objectref, as follows:
? If the entry count of the monitor associated with objectref is zero, the thread enters the monitor and sets its entry count to one. The thread is then the owner of the monitor.
? If the thread already owns the monitor associated with objectref, it reenters the monitor, incrementing its entry count.
? If another thread already owns the monitor associated with objectref, the thread blocks until the monitor's entry count is zero, then tries again to gain ownership.
這段話的大概意思為:
每個(gè)對(duì)象有一個(gè)監(jiān)視器鎖(monitor).當(dāng)monitor被占用時(shí)就會(huì)處于鎖定狀態(tài)簇搅,線程執(zhí)行monitorenter指令時(shí)嘗試獲取monitor的所有權(quán)奋岁,過程如下:
- 如果monitor的進(jìn)入數(shù)為0晃痴,則該線程進(jìn)入monitor咽白,然后將進(jìn)入數(shù)設(shè)置為1,該線程即為monitor的所有者.
- 如果線程已經(jīng)占有該monitor盔夜,只是重新進(jìn)入撩鹿,則進(jìn)入monitor的進(jìn)入數(shù)加1.
- 如果其他線程已經(jīng)占用了monitor,則該線程進(jìn)入阻塞狀態(tài)盟蚣,直到monitor的進(jìn)入數(shù)為0黍析,再重新嘗試獲取monitor的所有權(quán).
monitorexit
The thread that executes monitorexit must be the owner of the monitor associated with the instance referenced by objectref.
The thread decrements the entry count of the monitor associated with objectref. If as a result the value of the entry count is zero, the thread exits the monitor and is no longer its owner. Other threads that are blocking to enter the monitor are allowed to attempt to do so.
這段話的大概意思為:
執(zhí)行monitorexit的線程必須是objectref所對(duì)應(yīng)的monitor的所有者.
指令執(zhí)行時(shí),monitor的進(jìn)入數(shù)減1屎开,如果減1后進(jìn)入數(shù)為0阐枣,那線程退出monitor,不再是這個(gè)monitor的所有者.其他被這個(gè)monitor阻塞的線程可以嘗試去獲取這個(gè) monitor 的所有權(quán).
通過這兩段描述奄抽,我們應(yīng)該能很清楚的看出Synchronized的實(shí)現(xiàn)原理蔼两,Synchronized的語義底層是通過一個(gè)monitor的對(duì)象來完成,其實(shí)wait/notify等方法也依賴于monitor對(duì)象逞度,這就是為什么只有在同步的塊或者方法中才能調(diào)用wait/notify等方法额划,否則會(huì)拋出java.lang.IllegalMonitorStateException的異常的原因.
我們可以用同樣的方法查看同步方法(方法前用Synchronized修飾)的原理此處不在論述.
大致上我們可以認(rèn)為, Synchronized提供的鎖事悲觀鎖, 并且是在java指令層面實(shí)現(xiàn)的.
下面看java.util.concurrent的實(shí)現(xiàn).
以AtomicInteger為例, 其incrementAndGet的源碼如下:
public final int incrementAndGet() {
for (;;) {
int current = get();
int next = current + 1;
if (compareAndSet(current, next))
return next;
}
}
public final boolean compareAndSet(int expect, int update) {
return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
}
/**
* Atomically update Java variable to <tt>x</tt> if it is currently
* holding <tt>expected</tt>.
* @return <tt>true</tt> if successful
*/
public final native boolean compareAndSwapInt(Object o, long offset,
int expected,
int x);
跟蹤代碼發(fā)現(xiàn)其底層實(shí)現(xiàn)為unsafe.compareAndSwapInt(), 其底層的原子性由cpu提供.
與synchronized不同, java.util.concurent包中的同步器一般使用樂觀鎖(采用CAS的方式), 并且提供硬件級(jí)別的同步原語來提升性能.
2.2 AbstractQueuedSync
java.util.concurrent包為所有應(yīng)用層的同步器提供了一個(gè)抽象類AbstractQueuedSync, 其余同步器只需繼承該類即可實(shí)現(xiàn)一個(gè)高性能的同步器.
同步器背后的基本思想非常簡單, 只需兩個(gè)操作acquire(獲取資源)和release(釋放資源).
其偽代碼如下:
acquire:
while (synchronization state does not allow acquire) {
enqueue current thread if not already queued;
possibly block current thread;
}
dequeue current thread if it was queued;
release:
update synchronization state;
if (state may permit a blocked thread to acquire)
unblock one or more queued threads;
為了實(shí)現(xiàn)上述操作,需要下面三個(gè)基本組件的相互協(xié)作:
-同步狀態(tài)的原子性管理档泽;
-線程的阻塞與解除阻塞俊戳;
-隊(duì)列的管理;
2.2.1 同步狀態(tài)
AQS類使用單個(gè)int(32位)來保存同步狀態(tài),并暴露出getState馆匿、setState以及compareAndSet操作來讀取和更新這個(gè)狀態(tài).這些方法都依賴于j.u.c.atomic包的支持抑胎,這個(gè)包提供了兼容JSR133中volatile在讀和寫上的語義,并且通過使用本地的compare-and-swap或load-linked/store-conditional指令來實(shí)現(xiàn)compareAndSetState渐北,使得僅當(dāng)同步狀態(tài)擁有一個(gè)期望值的時(shí)候阿逃,才會(huì)被原子地設(shè)置成新值.
基于AQS的具體實(shí)現(xiàn)類必須根據(jù)暴露出的狀態(tài)相關(guān)的方法定義tryAcquire和tryRelease方法,以控制acquire和release操作.當(dāng)同步狀態(tài)滿足時(shí),tryAcquire方法必須返回true,而當(dāng)新的同步狀態(tài)允許后續(xù)acquire時(shí),tryRelease方法也必須返回true.這些方法都接受一個(gè)int類型的參數(shù)用于傳遞想要的狀態(tài).例如:可重入鎖中犹菱,當(dāng)某個(gè)線程從條件等待中返回,然后重新獲取鎖時(shí)肪跋,為了重新建立循環(huán)計(jì)數(shù)的場景.很多同步器并不需要這樣一個(gè)參數(shù),因此忽略它即可.
2.2.2 阻塞
在JSR166之前炼团,阻塞線程和解除線程阻塞都是基于Java內(nèi)置管程,沒有其它非基于Java內(nèi)置管程的API可以用來創(chuàng)建同步器.唯一可以選擇的是Thread.suspend和Thread.resume疏尿,但是它們都有無法解決的競態(tài)問題瘟芝,所以也沒法用:當(dāng)一個(gè)非阻塞的線程在一個(gè)正準(zhǔn)備阻塞的線程調(diào)用suspend前調(diào)用了resume,這個(gè)resume操作將不會(huì)有什么效果.
j.u.c包有一個(gè)LockSuport類褥琐,這個(gè)類中包含了解決這個(gè)問題的方法.方法LockSupport.park阻塞當(dāng)前線程除非/直到有個(gè)LockSupport.unpark方法被調(diào)用(unpark方法被提前調(diào)用也是可以的).unpark的調(diào)用是沒有被計(jì)數(shù)的锌俱,因此在一個(gè)park調(diào)用前多次調(diào)用unpark方法只會(huì)解除一個(gè)park操作.另外,它們作用于每個(gè)線程而不是每個(gè)同步器.一個(gè)線程在一個(gè)新的同步器上調(diào)用park操作可能會(huì)立即返回敌呈,因?yàn)樵诖酥翱赡苡小笆S嗟摹眜npark操作.但是贸宏,在缺少一個(gè)unpark操作時(shí),下一次調(diào)用park就會(huì)阻塞.雖然可以顯式地消除這個(gè)狀態(tài)磕洪,但并不值得這樣做.在需要的時(shí)候多次調(diào)用park會(huì)更高效.
這個(gè)簡單的機(jī)制與有些用法在某種程度上是相似的吭练,例如Solaris-9的線程庫,WIN32中的“可消費(fèi)事件”析显,以及Linux中的NPTL線程庫.因此最常見的運(yùn)行Java的平臺(tái)上都有相對(duì)應(yīng)的有效實(shí)現(xiàn).(但目前Solaris和Linux上的Sun Hotspot JVM參考實(shí)現(xiàn)實(shí)際上是使用一個(gè)pthread的condvar來適應(yīng)目前的運(yùn)行時(shí)設(shè)計(jì)的).park方法同樣支持可選的相對(duì)或絕對(duì)的超時(shí)設(shè)置鲫咽,以及與JVM的Thread.interrupt結(jié)合--可通過中斷來unpark一個(gè)線程.
2.2.3 隊(duì)列
整個(gè)框架的關(guān)鍵就是如何管理被阻塞的線程的隊(duì)列,該隊(duì)列是嚴(yán)格的FIFO隊(duì)列谷异,因此分尸,框架不支持基于優(yōu)先級(jí)的同步.
AQS維護(hù)一個(gè)CLH鏈表隊(duì)列, 通過兩個(gè)字段head和tail來存取, 這兩個(gè)字段是可原子更新的, 兩者在初始化時(shí)都指向了一個(gè)空節(jié)點(diǎn).
一個(gè)新的節(jié)點(diǎn),node歹嘹,通過一個(gè)原子操作入隊(duì):
do {
pred = tail;
} while(!tail.compareAndSet(pred, node));
每一個(gè)節(jié)點(diǎn)的“釋放”狀態(tài)都保存在其前驅(qū)節(jié)點(diǎn)中. 因此AQS作為自旋鎖的"自旋"操作就如下:
while (pred.status != RELEASED); // spin
自旋后的出隊(duì)操作只需將head字段指向剛剛得到鎖的節(jié)點(diǎn):
head = node;
CLH鎖的優(yōu)點(diǎn)在于其入隊(duì)和出隊(duì)操作是快速箩绍、無鎖的,以及無障礙的(即使在競爭下尺上,某個(gè)線程總會(huì)贏得一次插入機(jī)會(huì)而能繼續(xù)執(zhí)行)材蛛;且探測是否有線程正在等待也很快(只要測試一下head是否與tail相等);同時(shí)怎抛,“釋放”狀態(tài)是分散的仰税,避免了一些不必要的內(nèi)存競爭.
在原始版本的CLH鎖中,節(jié)點(diǎn)間甚至都沒有互相鏈接.自旋鎖中抽诉,pred變量可以是一個(gè)局部變量.然而陨簇,Scott和Scherer證明了通過在節(jié)點(diǎn)中顯式地維護(hù)前驅(qū)節(jié)點(diǎn),CLH鎖就可以處理“超時(shí)”和各種形式的“取消”:如果一個(gè)節(jié)點(diǎn)的前驅(qū)節(jié)點(diǎn)取消了,這個(gè)節(jié)點(diǎn)就可以滑動(dòng)去使用前面一個(gè)節(jié)點(diǎn)的狀態(tài)字段.
為了將CLH隊(duì)列用于阻塞式同步器河绽,需要做些額外的修改以提供一種高效的方式定位某個(gè)節(jié)點(diǎn)的后繼節(jié)點(diǎn).在自旋鎖中己单,一個(gè)節(jié)點(diǎn)只需要改變其狀態(tài),下一次自旋中其后繼節(jié)點(diǎn)就能注意到這個(gè)改變耙饰,所以節(jié)點(diǎn)間的鏈接并不是必須的.但在阻塞式同步器中纹笼,一個(gè)節(jié)點(diǎn)需要顯式地喚醒(unpark)其后繼節(jié)點(diǎn).
AQS隊(duì)列的節(jié)點(diǎn)包含一個(gè)next鏈接到它的后繼節(jié)點(diǎn).但是,由于沒有針對(duì)雙向鏈表節(jié)點(diǎn)的類似compareAndSet的原子性無鎖插入指令苟跪,因此這個(gè)next鏈接的設(shè)置并非作為原子性插入操作的一部分廷痘,而僅是在節(jié)點(diǎn)被插入后簡單地賦值:
pred.next = node;
next鏈接僅是一種優(yōu)化.如果通過某個(gè)節(jié)點(diǎn)的next字段發(fā)現(xiàn)其后繼結(jié)點(diǎn)不存在(或看似被取消了),總是可以使用pred字段從尾部開始向前遍歷來檢查是否真的有后續(xù)節(jié)點(diǎn).
第二個(gè)對(duì)CLH隊(duì)列主要的修改是將每個(gè)節(jié)點(diǎn)都有的狀態(tài)字段用于控制阻塞而非自旋.在同步器框架中件已,僅在線程調(diào)用具體子類中的tryAcquire方法返回true時(shí)笋额,隊(duì)列中的線程才能從acquire操作中返回;而單個(gè)“released”位是不夠的.但仍然需要做些控制以確保當(dāng)一個(gè)活動(dòng)的線程位于隊(duì)列頭部時(shí)篷扩,僅允許其調(diào)用tryAcquire兄猩;這時(shí)的acquire可能會(huì)失敗,然后(重新)阻塞.這種情況不需要讀取狀態(tài)標(biāo)識(shí)鉴未,因?yàn)榭梢酝ㄟ^檢查當(dāng)前節(jié)點(diǎn)的前驅(qū)是否為head來確定權(quán)限.與自旋鎖不同枢冤,讀取head以保證復(fù)制時(shí)不會(huì)有太多的內(nèi)存競爭( there is not enough memory contention reading head to warrant replication.).然而,“取消”狀態(tài)必須存在于狀態(tài)字段中.
隊(duì)列節(jié)點(diǎn)的狀態(tài)字段也用于避免沒有必要的park和unpark調(diào)用.雖然這些方法跟阻塞原語一樣快铜秆,但在跨越Java和JVM runtime以及操作系統(tǒng)邊界時(shí)仍有可避免的開銷.在調(diào)用park前淹真,線程設(shè)置一個(gè)“喚醒(signal me)”位,然后再一次檢查同步和節(jié)點(diǎn)狀態(tài).一個(gè)釋放的線程會(huì)清空其自身狀態(tài).這樣線程就不必頻繁地嘗試阻塞连茧,特別是在鎖相關(guān)的類中趟咆,這樣會(huì)浪費(fèi)時(shí)間等待下一個(gè)符合條件的線程去申請(qǐng)鎖,從而加劇其它競爭的影響.除非后繼節(jié)點(diǎn)設(shè)置了“喚醒”位梅屉,否則這也可避免正在release的線程去判斷其后繼節(jié)點(diǎn).這反過來也消除了這些情形:除非“喚醒”與“取消”同時(shí)發(fā)生值纱,否則必須遍歷多個(gè)節(jié)點(diǎn)來處理一個(gè)似乎為null的next字段.
同步框架中使用的CLH鎖的變體與其他語言中的相比,主要區(qū)別可能是同步框架中使用的CLH鎖需要依賴?yán)厥展芾砉?jié)點(diǎn)的內(nèi)存坯汤,這就避免了一些復(fù)雜性和開銷.但是虐唠,即使依賴GC也仍然需要在確定鏈接字段不再需要時(shí)將其置為null.這往往可以與出隊(duì)操作一起完成.否則,無用的節(jié)點(diǎn)仍然可觸及惰聂,它們就沒法被回收.
其它一些更深入的微調(diào)疆偿,包括CLH隊(duì)列首次遇到競爭時(shí)才需要的初始空節(jié)點(diǎn)的延遲初始化等,都可以在J2SE1.5的版本的源代碼文檔中找到相應(yīng)的描述.
拋開這些細(xì)節(jié)搓幌,基本的acquire操作的最終實(shí)現(xiàn)的一般形式如下(互斥杆故,非中斷,無超時(shí)):
if(!tryAcquire(arg)) {
node = create and enqueue new node;
pred = node's effective predecessor;
while (pred is not head node || !tryAcquire(arg)) {
if (pred's signal bit is set)
pard()
else
compareAndSet pred's signal bit to true;
pred = node's effective predecessor;
}
head = node;
}
release操作:
if(tryRelease(arg) && head node's signal bit is set) {
compareAndSet head's bit to false;
unpark head's successor, if one exist
}
acquire操作的主循環(huán)次數(shù)依賴于具體實(shí)現(xiàn)類中tryAcquire的實(shí)現(xiàn)方式.另一方面溉愁,在沒有“取消”操作的情況下处铛,每一個(gè)組件的acquire和release都是一個(gè)O(1)的操作,忽略park中發(fā)生的所有操作系統(tǒng)線程調(diào)度.
支持“取消”操作主要是要在acquire循環(huán)里的park返回時(shí)檢查中斷或超時(shí).由超時(shí)或中斷而被取消等待的線程會(huì)設(shè)置其節(jié)點(diǎn)狀態(tài),然后unpark其后繼節(jié)點(diǎn).在有“取消”的情況下撤蟆,判斷其前驅(qū)節(jié)點(diǎn)和后繼節(jié)點(diǎn)以及重置狀態(tài)可能需要O(n)的遍歷(n是隊(duì)列的長度).由于“取消”操作奕塑,該線程再也不會(huì)被阻塞,節(jié)點(diǎn)的鏈接和狀態(tài)字段可以被快速重建.
2.2.4 條件隊(duì)列
AQS框架提供了一個(gè)ConditionObject類家肯,給維護(hù)獨(dú)占同步的類以及實(shí)現(xiàn)Lock接口的類使用.一個(gè)鎖對(duì)象可以關(guān)聯(lián)任意數(shù)目的條件對(duì)象龄砰,可以提供典型的管程風(fēng)格的await、signal和signalAll操作讨衣,包括帶有超時(shí)的换棚,以及一些檢測、監(jiān)控的方法.
通過修正一些設(shè)計(jì)決策反镇,ConditionObject類有效地將條件(conditions)與其它同步操作結(jié)合到了一起.該類只支持Java風(fēng)格的管程訪問規(guī)則固蚤,這些規(guī)則中,僅當(dāng)當(dāng)前線程持有鎖且要操作的條件(condition)屬于該鎖時(shí)愿险,條件操作才是合法的.這樣颇蜡,一個(gè)ConditionObject關(guān)聯(lián)到一個(gè) ReentrantLock 上就表現(xiàn)的跟內(nèi)置的管程(通過Object.wait等)一樣了.兩者的不同僅僅在于方法的名稱价说、額外的功能以及用戶可以為每個(gè)鎖聲明多個(gè)條件.
ConditionObject使用了與同步器一樣的內(nèi)部隊(duì)列節(jié)點(diǎn).但是辆亏,是在一個(gè)單獨(dú)的條件隊(duì)列中維護(hù)這些節(jié)點(diǎn)的.signal操作是通過將節(jié)點(diǎn)從條件隊(duì)列轉(zhuǎn)移到鎖隊(duì)列中來實(shí)現(xiàn)的,而沒有必要在需要喚醒的線程重新獲取到鎖之前將其喚醒.
基本的await操作如下:
create and add new node to conditon queue;
release lock;
block until node is on lock queue;
re-acquire lock;
signal操作如下:
transfer the first node from condition queue to lock queue;
因?yàn)橹挥性诔钟墟i的時(shí)候才能執(zhí)行這些操作鳖目,因此他們可以使用順序鏈表隊(duì)列操作來維護(hù)條件隊(duì)列(在節(jié)點(diǎn)中用一個(gè)nextWaiter字段).轉(zhuǎn)移操作僅僅把第一個(gè)節(jié)點(diǎn)從條件隊(duì)列中的鏈接解除扮叨,然后通過CLH插入操作將其插入到鎖隊(duì)列上.
為了維護(hù)適當(dāng)?shù)捻樞颍?duì)列節(jié)點(diǎn)狀態(tài)變量中的一個(gè)位記錄了該節(jié)點(diǎn)是否已經(jīng)(或正在)被轉(zhuǎn)移.“喚醒”和“取消”相關(guān)的代碼都會(huì)嘗試用compareAndSet修改這個(gè)狀態(tài).如果某次signal操作修改失敗领迈,就會(huì)轉(zhuǎn)移隊(duì)列中的下一個(gè)節(jié)點(diǎn)(如果存在的話).如果某次“取消”操作修改失敗彻磁,就必須中止此次轉(zhuǎn)移,然后等待重新獲得鎖.后面的情況采用了一個(gè)潛在的無限的自旋等待.在節(jié)點(diǎn)成功的被插到鎖隊(duì)列之前狸捅,被“取消”的等待不能重新獲得鎖衷蜓,所以必須自旋等待CLH隊(duì)列插入(即compareAndSet操作)被“喚醒”線程成功執(zhí)行.這里極少需要自旋,且自旋里使用Thread.yield來提示應(yīng)該調(diào)度某一其它線程尘喝,理想情況下就是執(zhí)行signal的那個(gè)線程.雖然有可能在這里為“取消”實(shí)現(xiàn)一個(gè)幫助策略以幫助插入節(jié)點(diǎn)磁浇,但這種情況實(shí)在太少,找不到合適的理由來增加這些開銷.在其它所有的情況下朽褪,這個(gè)基本的機(jī)制都不需要自旋或yield置吓,因此在單處理器上保持著合理的性能.
3. 利用AQS實(shí)現(xiàn)同步器
AQS已經(jīng)定義了同步器的基本功能, 要實(shí)現(xiàn)一個(gè)應(yīng)用層面的同步器, 只需要繼承該類并實(shí)現(xiàn)tryAcquire和tryRelease方法即可.
下面是一個(gè)最簡單的Mutex類的實(shí)現(xiàn),它使用同步狀態(tài)0表示解鎖缔赠,1表示鎖定.這個(gè)類并不需要同步方法中的參數(shù)衍锚,因此這里在調(diào)用的時(shí)候使用0作為實(shí)參,方法實(shí)現(xiàn)里將其忽略.
class Mutex {
class Sync extends AbstractQueuedSynchronizer {
public boolean tryAcquire(int ignore) {
return compareAndSetState(0, 1);
}
public boolean tryRelease(int ignore) {
setState(0); return true;
}
}
private final Sync sync = new Sync();
public void lock() { sync.acquire(0); }
public void unlock() { sync.release(0); }
}
下面是java.util.concurrent包中同步器定義方式的概述:
ReentrantLock類使用AQS同步狀態(tài)來保存鎖(重復(fù))持有的次數(shù).當(dāng)鎖被一個(gè)線程獲取時(shí)嗤堰,ReentrantLock也會(huì)記錄下當(dāng)前獲得鎖的線程標(biāo)識(shí)戴质,以便檢查是否是重復(fù)獲取,以及當(dāng)錯(cuò)誤的線程(譯者注:如果線程不是鎖的持有者,在此線程中執(zhí)行該鎖的unlock操作就是非法的)試圖進(jìn)行解鎖操作時(shí)檢測是否存在非法狀態(tài)異常.ReentrantLock也使用了AQS提供的ConditionObject置森,還向外暴露了其它監(jiān)控和監(jiān)測相關(guān)的方法.ReentrantLock通過在內(nèi)部聲明兩個(gè)不同的AbstractQueuedSynchronizer實(shí)現(xiàn)類(提供公平模式的那個(gè)禁用了闖入策略)來實(shí)現(xiàn)可選的公平模式斗埂,在創(chuàng)建ReentrantLock實(shí)例的時(shí)候根據(jù)設(shè)置(譯者注:即ReentrantLock構(gòu)造方法中的fair參數(shù))使用相應(yīng)的AbstractQueuedSynchronizer實(shí)現(xiàn)類.
ReentrantReadWriteLock類使用AQS同步狀態(tài)中的16位來保存寫鎖持有的次數(shù),剩下的16位用來保存讀鎖的持有次數(shù).WriteLock的構(gòu)建方式同ReentrantLock.ReadLock則通過使用acquireShared方法來支持同時(shí)允許多個(gè)讀線程.
Semaphore類(計(jì)數(shù)信號(hào)量)使用AQS同步狀態(tài)來保存信號(hào)量的當(dāng)前計(jì)數(shù).它里面定義的acquireShared方法會(huì)減少計(jì)數(shù)凫海,或當(dāng)計(jì)數(shù)為非正值時(shí)阻塞線程呛凶;tryRelease方法會(huì)增加計(jì)數(shù),可能在計(jì)數(shù)為正值時(shí)還要解除線程的阻塞.
CountDownLatch類使用AQS同步狀態(tài)來表示計(jì)數(shù).當(dāng)該計(jì)數(shù)為0時(shí)行贪,所有的acquire操作(譯者注:acquire操作是從aqs的角度說的漾稀,對(duì)應(yīng)到CountDownLatch中就是await方法)才能通過.
FutureTask類使用AQS同步狀態(tài)來表示某個(gè)異步計(jì)算任務(wù)的運(yùn)行狀態(tài)(初始化、運(yùn)行中建瘫、被取消和完成).設(shè)置(譯者注:FutureTask的set方法)或取消(譯者注:FutureTask的cancel方法)一個(gè)FutureTask時(shí)會(huì)調(diào)用AQS的release操作崭捍,等待計(jì)算結(jié)果的線程的阻塞解除是通過AQS的acquire操作實(shí)現(xiàn)的.
SynchronousQueues類(一種CSP(Communicating Sequential Processes)形式的傳遞)使用了內(nèi)部的等待節(jié)點(diǎn),這些節(jié)點(diǎn)可以用于協(xié)調(diào)生產(chǎn)者和消費(fèi)者.同時(shí)啰脚,它使用AQS同步狀態(tài)來控制當(dāng)某個(gè)消費(fèi)者消費(fèi)當(dāng)前一項(xiàng)時(shí)殷蛇,允許一個(gè)生產(chǎn)者繼續(xù)生產(chǎn),反之亦然.
4. 總結(jié)
總而言是, java.util.concurrent包中的同步框架使用硬件級(jí)別的同步原語和CAS的方式提升了java內(nèi)置同步工具9synchronized)的性能, 另外還提供了一個(gè)AQS框架, 是的實(shí)現(xiàn)一個(gè)高性能的同步器變得非常方便, 只需繼承AQS, 并實(shí)現(xiàn)tryAcquire和tryRelease方法即可.