概述
在學習java并發(fā)編程的過程中侨把,java.util.concurrent包是我們需要學習和理解的關鍵之一,concurrent包的作者Doug Lea在其中為我們提供了大量實用铛漓,高性能的工具愕贡。在并發(fā)編程的過程中潦嘶,這些工具也得到了廣泛的應用创南。
Concurrent包的層次結構
打開java.util.concurrent
包伦忠,我們會發(fā)現其中包含了兩個子包atomic
和locks
,另外還有很多非阻塞隊列和ThreadPoolExecutor
稿辙,而這些就是concurrent包的精華昆码。后面我也會對其中的關鍵類的源碼進行逐個研讀,增加自己對并發(fā)編程的理解邻储。
分析整個concurrent包赋咽,得出如下concurrent整體實現示意圖,如下:
Lock接口
鎖是用來控制多線程訪問共享資源的方式芥备,一般在說冬耿,一個鎖能防止多個線程同時訪問共享資源(但是也有些鎖允許多個線程同時訪問共享資源,例如讀寫鎖)萌壳。在Lock接口出現之前,java程序靠synchronized關鍵字來實現鎖功能日月,在Java SE5之后袱瓮,并發(fā)包新增了Lock接口和相關實現類用來實現鎖功能,它提供了與synchronized關鍵字相似的功能爱咬,只是在使用時需要顯示的獲取鎖和釋放鎖尺借。雖然它失去了synchronized提供的隱式獲取和釋放鎖的便捷性,但是卻擁有了鎖獲取和釋放的可操作性精拟、可中斷獲取鎖以及超時獲取鎖等多種synchronized不具備的特性燎斩。(摘自:《Java并發(fā)編程的藝術》)
Lock的使用也非常簡單,通常我們顯示的使用鎖方式如下:
ReentrantLock reentrantLock = new ReentrantLock();
//獲取鎖
reentrantLock.lock();
try {
//業(yè)務邏輯處理
...
}catch (Exception e){
//異常捕獲
...
}finally {
//釋放鎖
reentrantLock.unlock();
}
這里需要注意的是蜂绎,一定要在finally中釋放鎖栅表,目的是保證在獲取到鎖之后,最終能夠被釋放师枣。
Lock的API
接下來我們來看下Lock接口中提供的幾種方法:
對這幾種方法的解釋怪瓶,從源碼的注釋中翻譯過來大概可以做出以下總結:
方法名稱 | 描述 |
---|---|
void lock() |
獲取鎖,當前線程獲取到鎖后返回 |
void lockInterruptibly() |
可中斷的獲取鎖践美,該方法會響應中斷洗贰,即在鎖的獲取過程中可以中斷線程 |
boolean tryLock() |
非阻塞的獲取鎖找岖,如果獲取到鎖了返回true,否則返回false |
boolean tryLock(long,TimeUnit) |
超時獲取鎖敛滋,在超時時間內或未中斷的情況下能夠獲取到鎖许布,超時后會返回false |
void unLock() |
釋放鎖 |
Condition newCondition() |
獲取與lock綁定的等待通知組件,當前線程必須獲得了鎖才能進行等待绎晃,進行等待時會先釋放鎖蜜唾,當再次獲取鎖時才能從等待中返回 |
下面我們來簡單看下實現了Lock接口的類是怎樣來實現這五個方法的,以ReentrantLock為例箕昭,可以看下摘出來的部分源碼:
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer{...}
public void lock() {
sync.lock();
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock() {
return sync.nonfairTryAcquire(1);
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
...
從上面的源碼可以發(fā)現灵妨,ReentrantLock本身對Lock接口的5個方法實現都非常簡單,其核心都是調用了一個繼承于AbstractQueuedSynchronizer(AQS)的類落竹。所以想要理解Lock接口這五個方法的詳細實現泌霍,那么理解AQS就非常必要了。
初識AQS
AQS又稱隊列同步器述召,它是用來構建鎖或者其他同步組件的基礎框架朱转,它使用了一個int state成員變量來表示同步狀態(tài),通過內置的FIFO隊列來完成資源獲取線程的排隊工作。AQS的主要使用方法是繼承积暖,子類通過繼承AQS并實現它的抽象方法來管理同步狀態(tài)藤为,而對狀態(tài)的修改,AQS提供了3個方法:getState()
,setState(int newState)
和compareAndSetState(int expect,int update)
夺刑,這三個方法可以保證對狀態(tài)的修改是安全的缅疟。
AQS的子類推薦被定義為自定義同步組件的靜態(tài)內部類,同步器自身沒有實現任何同步接口遍愿,它僅僅是定義了若干同步狀態(tài)獲取和釋放的方法來供自定義同步組件使用存淫,同步器既可以支持獨占式的獲取同步狀態(tài),也可以支持共享式的獲取同步狀態(tài)沼填,這樣就可以方便的實現不同類型的同步組件(ReentrantLock
桅咆、ReentrantReadWriteLock
和CountDownLatch
等)。
同步器是實現鎖(或任意同步組件)的關鍵坞笙,在鎖的實現中聚合同步器岩饼,利用同步器實現鎖的語義⊙σ梗可以這樣理解二者的關系:鎖是面向使用者的籍茧,它定義了使用者與鎖交互的接口,隱藏了實現細節(jié)却邓;同步器面向的是鎖的實現者硕糊,它簡化了鎖的實現方式,屏蔽同步狀態(tài)管理、線程的排隊简十、等待和喚醒等底層操作檬某。鎖和同步器很好的隔離了使用者和實現者鎖關注的領域。
AQS的接口與示例
AQS同步器的設計是基于模板方法模式的,使用者需要繼承同步器并重寫指定的方法螟蝙,隨后將同步器組合在自定義同步組件中恢恼,并調用同步器提供的模板方法,而這些方法又會調用使用者重寫的方法胰默。這里可能有點繞场斑,我們還是以ReentrantLock為例,摘取部分源碼來看下
AQS中需要重寫tryAcquire()方法:
protected boolean tryAcquire(int arg) {
throw new UnsupportedOperationException();
}
在ReentrantLock類中繼承自AQS的內部類NonfairSync是這樣實現tryAcquire的:
static final class NonfairSync extends Sync {
......
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
abstract static class Sync extends AbstractQueuedSynchronizer {
......
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
......
在AQS模板方法acquire()中調用了子類實現的tryAcquire()方法:
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
當繼承AQS的NonfairSync調用模板方法acquire時就會調用已經被NonfairSync重寫的tryAcquire方法,這就是使用AQS的方式牵署。在重寫同步器指定的方法時漏隐,需要使用下面這三個方法來訪問或修改同步狀態(tài):
方法名 | 描述 |
---|---|
getState() | 獲取當前同步狀態(tài) |
setState(int newState) | 設置當前同步狀態(tài) |
compareAndSetState(int expect,int new State) | 使用CAS設置當前狀態(tài),該方法能夠保證設置狀態(tài)的原子性 |
AQS可重寫的方法和描述奴迅,我從《Java并發(fā)編程的藝術》一書中摘取過來如下:
在實現自定義同步組件時青责, 將會調用同步器提供的模板方法,這些模板方法和描述如下:
同步器提供的模板方法基本上可以分為以下三類:
- 獨占式獲取和釋放同步狀態(tài)取具;
- 共享式獲取和釋放同步狀態(tài)脖隶;
- 查詢同步隊列中的線程等待情況。
只有掌握了同步器的工作原理暇检,才能更深入的理解并發(fā)包中其他的并發(fā)組件产阱,下面寫一個例子來看看。
public class Mutex implements Lock{
private final Sync sync = new Sync();
@Override
public void lock() {
sync.acquire(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
@Override
public boolean tryLock() {
return sync.tryAcquire(1);
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1,unit.toNanos(time));
}
@Override
public void unlock() {
sync.release(0);
}
@Override
public Condition newCondition() {
return sync.newCondition();
}
private static class Sync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
if(compareAndSetState(0,1)){
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
if(getState() == 0) {throw new IllegalStateException();}
setExclusiveOwnerThread(null);
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
return getState() == 1;
}
final ConditionObject newCondition() {
return new ConditionObject();
}
}
public static void main(String[] args) {
Mutex mutex = new Mutex();
for (int i = 0 ; i< 10 ; i ++){
Thread thread = new Thread(){
@Override
public void run(){
System.out.println("線程:"+Thread.currentThread().getName()+ " 創(chuàng)建成功块仆!");
// if(mutex.tryLock()){
// try {
// System.out.println("線程: "+Thread.currentThread().getName()+" 獲取到鎖构蹬!");
// Thread.sleep(3*1000);
// } catch (InterruptedException e) {
// e.printStackTrace();
// } finally {
// System.out.println("線程: "+Thread.currentThread().getName()+" 釋放鎖!");
// mutex.unlock();
// }
// }
try {
mutex.lock();
System.out.println("線程: "+Thread.currentThread().getName()+" 獲取到鎖悔据!");
Thread.sleep(3*1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println("線程: "+Thread.currentThread().getName()+" 釋放鎖怎燥!");
mutex.unlock();
}
}
};
thread.start();
}
}
}
執(zhí)行情況如下圖:
上面的例子中,獨占鎖Mutex是一個自定義的同步組件蜜暑,它在同一時刻只允許一個線程占有鎖。Mutex中定義了一個靜態(tài)內部類策肝,該內部類繼承了同步器并實現了獨占式獲取和釋放同步狀態(tài)的方法肛捍。在main方法中,我用for循環(huán)創(chuàng)建了10個線程之众,run方法中有兩種獲取鎖的方式拙毫,mutex.lock()和mutex.tryLock()。查看程序執(zhí)行情況棺禾,可以發(fā)現缀蹄,當Thread-8獲取到獨占鎖時,其余線程都處于等待狀態(tài)(阻塞中)。如果執(zhí)行tryLock()方法話缺前,當Thread-8獲取到線程后,其余線程獲取不到鎖會蛀醉,會直接結束。從這個例子中看到衅码,Mutex在對state狀態(tài)的修改時拯刁,調用了getState(),setState(),compareAndSetState()方法,在lock()方法中逝段,只需要調用AQS的模板方法acquire()垛玻。
可以發(fā)現實現一個自定義同步器,主要是利用AQS奶躯,AQS屏蔽了同步狀態(tài)修改帚桩、線程排隊等底層實現,大大降低了實現一個可靠自定義同步組件的門檻嘹黔。
后面我會對AQS的源碼進行更深層次的學習账嚎,來提升自己對同步器的理解。