原創(chuàng)文章&經(jīng)驗(yàn)總結(jié)&從校招到A廠一路陽光一路滄桑
詳情請戳www.codercc.com
1. concurrent包的結(jié)構(gòu)層次
在針對并發(fā)編程中思劳,Doug Lea大師為我們提供了大量實(shí)用迅矛,高性能的工具類,針對這些代碼進(jìn)行研究會讓我們隊(duì)并發(fā)編程的掌握更加透徹也會大大提升我們隊(duì)并發(fā)編程技術(shù)的熱愛敢艰。這些代碼在java.util.concurrent包下诬乞。如下圖,即為concurrent包的目錄結(jié)構(gòu)圖钠导。
其中包含了兩個子包:atomic以及l(fā)ock震嫉,另外在concurrent下的阻塞隊(duì)列以及executors,這些就是concurrent包中的精華,之后會一一進(jìn)行學(xué)習(xí)牡属。而這些類的實(shí)現(xiàn)主要是依賴于volatile以及CAS(關(guān)于volatile可以看這篇文章芽死,關(guān)于CAS可以看這篇文章的3.1節(jié)),從整體上來看concurrent包的整體實(shí)現(xiàn)圖如下圖所示:
2. lock簡介
我們下來看concurent包下的lock子包哄孤。鎖是用來控制多個線程訪問共享資源的方式钱贯,一般來說,一個鎖能夠防止多個線程同時訪問共享資源措伐。在Lock接口出現(xiàn)之前特纤,java程序主要是靠synchronized關(guān)鍵字實(shí)現(xiàn)鎖功能的,而java SE5之后侥加,并發(fā)包中增加了lock接口捧存,它提供了與synchronized一樣的鎖功能。雖然它失去了像synchronize關(guān)鍵字隱式加鎖解鎖的便捷性担败,但是卻擁有了鎖獲取和釋放的可操作性昔穴,可中斷的獲取鎖以及超時獲取鎖等多種synchronized關(guān)鍵字所不具備的同步特性。通常使用顯示使用lock的形式如下:
Lock lock = new ReentrantLock();
lock.lock();
try{
.......
}finally{
lock.unlock();
}
需要注意的是synchronized同步塊執(zhí)行完成或者遇到異常是鎖會自動釋放提前,而lock必須調(diào)用unlock()方法釋放鎖吗货,因此在finally塊中釋放鎖。
2.1 Lock接口API
我們現(xiàn)在就來看看lock接口定義了哪些方法:
void lock(); //獲取鎖
void lockInterruptibly() throws InterruptedException狈网;//獲取鎖的過程能夠響應(yīng)中斷
boolean tryLock();//非阻塞式響應(yīng)中斷能立即返回宙搬,獲取鎖放回true反之返回fasle
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;//超時獲取鎖,在超時內(nèi)或者未中斷的情況下能夠獲取鎖
Condition newCondition();//獲取與lock綁定的等待通知組件拓哺,當(dāng)前線程必須獲得了鎖才能進(jìn)行等待害淤,進(jìn)行等待時會先釋放鎖,當(dāng)再次獲取鎖時才能從等待中返回
上面是lock接口下的五個方法拓售,也只是從源碼中英譯中翻譯了一遍窥摄,感興趣的可以自己的去看看。那么在locks包下有哪些類實(shí)現(xiàn)了該接口了础淤?先從最熟悉的ReentrantLock說起崭放。
public class ReentrantLock implements Lock, java.io.Serializable
很顯然ReentrantLock實(shí)現(xiàn)了lock接口哨苛,接下來我們來仔細(xì)研究一下它是怎樣實(shí)現(xiàn)的。當(dāng)你查看源碼時你會驚訝的發(fā)現(xiàn)ReentrantLock并沒有多少代碼币砂,另外有一個很明顯的特點(diǎn)是:基本上所有的方法的實(shí)現(xiàn)實(shí)際上都是調(diào)用了其靜態(tài)內(nèi)存類Sync
中的方法建峭,而Sync類繼承了AbstractQueuedSynchronizer(AQS)
【龃荩可以看出要想理解ReentrantLock關(guān)鍵核心在于對隊(duì)列同步器AbstractQueuedSynchronizer(簡稱同步器)的理解亿蒸。
2.2 初識AQS
關(guān)于AQS在源碼中有十分具體的解釋:
Provides a framework for implementing blocking locks and related
synchronizers (semaphores, events, etc) that rely on
first-in-first-out (FIFO) wait queues. This class is designed to
be a useful basis for most kinds of synchronizers that rely on a
single atomic {@code int} value to represent state. Subclasses
must define the protected methods that change this state, and which
define what that state means in terms of this object being acquired
or released. Given these, the other methods in this class carry
out all queuing and blocking mechanics. Subclasses can maintain
other state fields, but only the atomically updated {@code int}
value manipulated using methods {@link #getState}, {@link
#setState} and {@link #compareAndSetState} is tracked with respect
to synchronization.
<p>Subclasses should be defined as non-public internal helper
classes that are used to implement the synchronization properties
of their enclosing class. Class
{@code AbstractQueuedSynchronizer} does not implement any
synchronization interface. Instead it defines methods such as
{@link #acquireInterruptibly} that can be invoked as
appropriate by concrete locks and related synchronizers to
implement their public methods.
同步器是用來構(gòu)建鎖和其他同步組件的基礎(chǔ)框架,它的實(shí)現(xiàn)主要依賴一個int成員變量來表示同步狀態(tài)以及通過一個FIFO隊(duì)列構(gòu)成等待隊(duì)列掌桩。它的子類必須重寫AQS的幾個protected修飾的用來改變同步狀態(tài)的方法边锁,其他方法主要是實(shí)現(xiàn)了排隊(duì)和阻塞機(jī)制。狀態(tài)的更新使用getState,setState以及compareAndSetState這三個方法波岛。
子類被推薦定義為自定義同步組件的靜態(tài)內(nèi)部類茅坛,同步器自身沒有實(shí)現(xiàn)任何同步接口,它僅僅是定義了若干同步狀態(tài)的獲取和釋放方法來供自定義同步組件的使用则拷,同步器既支持獨(dú)占式獲取同步狀態(tài)贡蓖,也可以支持共享式獲取同步狀態(tài),這樣就可以方便的實(shí)現(xiàn)不同類型的同步組件煌茬。
同步器是實(shí)現(xiàn)鎖(也可以是任意同步組件)的關(guān)鍵斥铺,在鎖的實(shí)現(xiàn)中聚合同步器,利用同步器實(shí)現(xiàn)鎖的語義坛善×乐可以這樣理解二者的關(guān)系:鎖是面向使用者,它定義了使用者與鎖交互的接口浑吟,隱藏了實(shí)現(xiàn)細(xì)節(jié);同步器是面向鎖的實(shí)現(xiàn)者耗溜,它簡化了鎖的實(shí)現(xiàn)方式组力,屏蔽了同步狀態(tài)的管理,線程的排隊(duì)抖拴,等待和喚醒等底層操作燎字。鎖和同步器很好的隔離了使用者和實(shí)現(xiàn)者所需關(guān)注的領(lǐng)域。
2.3 AQS的模板方法設(shè)計(jì)模式
AQS的設(shè)計(jì)是使用模板方法設(shè)計(jì)模式阿宅,它將一些方法開放給子類進(jìn)行重寫候衍,而同步器給同步組件所提供模板方法又會重新調(diào)用被子類所重寫的方法。舉個例子洒放,AQS中需要重寫的方法tryAcquire:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
ReentrantLock中NonfairSync(繼承AQS)會重寫該方法為:
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
而AQS中的模板方法acquire():
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
會調(diào)用tryAcquire方法蛉鹿,而此時當(dāng)繼承AQS的NonfairSync調(diào)用模板方法acquire時就會調(diào)用已經(jīng)被NonfairSync重寫的tryAcquire方法。這就是使用AQS的方式往湿,在弄懂這點(diǎn)后會lock的實(shí)現(xiàn)理解有很大的提升妖异⊥锵罚可以歸納總結(jié)為這么幾點(diǎn):
- 同步組件(這里不僅僅值鎖,還包括CountDownLatch等)的實(shí)現(xiàn)依賴于同步器AQS他膳,在同步組件實(shí)現(xiàn)中响逢,使用AQS的方式被推薦定義繼承AQS的靜態(tài)內(nèi)存類;
- AQS采用模板方法進(jìn)行設(shè)計(jì)棕孙,AQS的protected修飾的方法需要由繼承AQS的子類進(jìn)行重寫實(shí)現(xiàn)舔亭,當(dāng)調(diào)用AQS的子類的方法時就會調(diào)用被重寫的方法;
- AQS負(fù)責(zé)同步狀態(tài)的管理蟀俊,線程的排隊(duì)钦铺,等待和喚醒這些底層操作,而Lock等同步組件主要專注于實(shí)現(xiàn)同步語義欧漱;
- 在重寫AQS的方式時职抡,使用AQS提供的
getState(),setState(),compareAndSetState()
方法進(jìn)行修改同步狀態(tài)
AQS可重寫的方法如下圖(摘自《java并發(fā)編程的藝術(shù)》一書):
在實(shí)現(xiàn)同步組件時AQS提供的模板方法如下圖:
AQS提供的模板方法可以分為3類:
- 獨(dú)占式獲取與釋放同步狀態(tài);
- 共享式獲取與釋放同步狀態(tài)误甚;
- 查詢同步隊(duì)列中等待線程情況缚甩;
同步組件通過AQS提供的模板方法實(shí)現(xiàn)自己的同步語義。
3. 一個例子
下面使用一個例子來進(jìn)一步理解下AQS的使用窑邦。這個例子也是來源于AQS源碼中的example擅威。
class Mutex implements Lock, java.io.Serializable {
// Our internal helper class
// 繼承AQS的靜態(tài)內(nèi)存類
// 重寫方法
private static class Sync extends AbstractQueuedSynchronizer {
// Reports whether in locked state
protected boolean isHeldExclusively() {
return getState() == 1;
}
// Acquires the lock if state is zero
public boolean tryAcquire(int acquires) {
assert acquires == 1; // Otherwise unused
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
// Releases the lock by setting state to zero
protected boolean tryRelease(int releases) {
assert releases == 1; // Otherwise unused
if (getState() == 0) throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
// Provides a Condition
Condition newCondition() {
return new ConditionObject();
}
// Deserializes properly
private void readObject(ObjectInputStream s)
throws IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
}
// The sync object does all the hard work. We just forward to it.
private final Sync sync = new Sync();
//使用同步器的模板方法實(shí)現(xiàn)自己的同步語義
public void lock() {
sync.acquire(1);
}
public boolean tryLock() {
return sync.tryAcquire(1);
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public boolean isLocked() {
return sync.isHeldExclusively();
}
public boolean hasQueuedThreads() {
return sync.hasQueuedThreads();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
}
MutexDemo:
public class MutextDemo {
private static Mutex mutex = new Mutex();
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
Thread thread = new Thread(() -> {
mutex.lock();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
mutex.unlock();
}
});
thread.start();
}
}
}
執(zhí)行情況:上面的這個例子實(shí)現(xiàn)了獨(dú)占鎖的語義,在同一個時刻只允許一個線程占有鎖冈钦。MutexDemo新建了10個線程郊丛,分別睡眠3s。從執(zhí)行情況也可以看出來當(dāng)前Thread-6正在執(zhí)行占有鎖而其他Thread-7,Thread-8等線程處于WAIT狀態(tài)瞧筛。按照推薦的方式厉熟,Mutex定義了一個繼承AQS的靜態(tài)內(nèi)部類Sync,并且重寫了AQS的tryAcquire等等方法,而對state的更新也是利用了setState(),getState()较幌,compareAndSetState()這三個方法揍瑟。在實(shí)現(xiàn)實(shí)現(xiàn)lock接口中的方法也只是調(diào)用了AQS提供的模板方法(因?yàn)镾ync繼承AQS)。從這個例子就可以很清楚的看出來乍炉,在同步組件的實(shí)現(xiàn)上主要是利用了AQS绢片,而AQS“屏蔽”了同步狀態(tài)的修改,線程排隊(duì)等底層實(shí)現(xiàn)岛琼,通過AQS的模板方法可以很方便的給同步組件的實(shí)現(xiàn)者進(jìn)行調(diào)用底循。而針對用戶來說,只需要調(diào)用同步組件提供的方法來實(shí)現(xiàn)并發(fā)編程即可槐瑞。同時在新建一個同步組件時需要把握的兩個關(guān)鍵點(diǎn)是:
- 實(shí)現(xiàn)同步組件時推薦定義繼承AQS的靜態(tài)內(nèi)存類熙涤,并重寫需要的protected修飾的方法;
- 同步組件語義的實(shí)現(xiàn)依賴于AQS的模板方法,而AQS模板方法又依賴于被AQS的子類所重寫的方法灭袁。
通俗點(diǎn)說猬错,因?yàn)锳QS整體設(shè)計(jì)思路采用模板方法設(shè)計(jì)模式,同步組件以及AQS的功能實(shí)際上別切分成各自的兩部分:
同步組件實(shí)現(xiàn)者的角度:
通過可重寫的方法:獨(dú)占式: tryAcquire()(獨(dú)占式獲取同步狀態(tài))茸歧,tryRelease()(獨(dú)占式釋放同步狀態(tài))倦炒;共享式 :tryAcquireShared()(共享式獲取同步狀態(tài)),tryReleaseShared()(共享式釋放同步狀態(tài))软瞎;告訴AQS怎樣判斷當(dāng)前同步狀態(tài)是否成功獲取或者是否成功釋放逢唤。同步組件專注于對當(dāng)前同步狀態(tài)的邏輯判斷,從而實(shí)現(xiàn)自己的同步語義涤浇。這句話比較抽象鳖藕,舉例來說,上面的Mutex例子中通過tryAcquire方法實(shí)現(xiàn)自己的同步語義只锭,在該方法中如果當(dāng)前同步狀態(tài)為0(即該同步組件沒被任何線程獲戎鳌),當(dāng)前線程可以獲取同時將狀態(tài)更改為1返回true蜻展,否則喉誊,該組件已經(jīng)被線程占用返回false。很顯然纵顾,該同步組件只能在同一時刻被線程占用伍茄,Mutex專注于獲取釋放的邏輯來實(shí)現(xiàn)自己想要表達(dá)的同步語義。
AQS的角度
而對AQS來說施逾,只需要同步組件返回的true和false即可敷矫,因?yàn)锳QS會對true和false會有不同的操作,true會認(rèn)為當(dāng)前線程獲取同步組件成功直接返回汉额,而false的話就AQS也會將當(dāng)前線程插入同步隊(duì)列等一系列的方法曹仗。
總的來說,同步組件通過重寫AQS的方法實(shí)現(xiàn)自己想要表達(dá)的同步語義蠕搜,而AQS只需要同步組件表達(dá)的true和false即可怎茫,AQS會針對true和false不同的情況做不同的處理,至于底層實(shí)現(xiàn)讥脐,可以看這篇文章遭居。
參考文獻(xiàn)
《java并發(fā)編程的藝術(shù)》