一、包的結構層次

其中包含了兩個子包atomic和locks,另外字concurrent下的阻塞佇列以及executor,這些就是concurrent包中的精華,而這些類的實作主要是依賴于volatile和CAS,從整體上看concurrent包的整體實作圖如下:

二、Lock和synchronized的比較
鎖是用來控制多個執行緒訪問共享資源的方式,一般來說,一個鎖能夠防止多個執行緒同時訪問共享資源,在Lock介面出現之前,Java主要是靠synchronized關鍵字實作鎖功能的,而Java1.5之后,并發包增加了Lock介面,它提供了與synchronized一樣的鎖功能,雖然它失去了想synchronized關鍵字隱式加鎖解鎖的便捷性,單卻擁有了獲取鎖和釋放鎖的可操作性,可中斷的獲取鎖以及超時獲取鎖等多種synchronized關鍵字所不具備的同步特性
通常Lock使用的形式如下:
Lock lock=new ReentrantLock();
lock.lock();
try {
} finally {
lock.unlock();
}
需要注意的是:synchronized同步塊執行完成活著遇到例外會自動釋放鎖,而lock必須呼叫unlock()釋放鎖,因此必須在finally中釋放鎖,
三、Lock介面API的介紹
看看Lock定義了哪些方法:
void lock();獲取鎖
void lockInterruptibly() throws InterruptedException;獲取所的程序能夠回應中斷
boolean tryLock();非阻塞式回應中斷能立即回傳,獲取鎖回傳true,反之回傳false;
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;超時獲取鎖,在超時內或者中斷的情況下能夠獲取鎖
Condition newCondition();獲取與lock系結的等待通知組件,當前執行緒必須獲得了鎖才能進行等待,進行等待時會先釋放鎖,當再次獲取鎖時才能從等待中回傳;
void unlock();釋放鎖
那么在locks包下有哪些類實作了改介面?
ReentrantLock
public class ReentrantLock implements Lock, java.io.Serializable
很顯然ReentrantLock實作了lock介面,當你查看原始碼是會發現ReentrantLock并沒有多少代碼,另外一個很明顯的特點是:基本上所有的方法的實作實際上都是呼叫了其靜態內部類Sync中的方法,而Sync類繼承了AbstractQueuedSynchronizer(AQS),可以看出ReentrantLock關鍵核心在于對佇列同步器AbstractQueuedSynchronizer的理解,
四、了解佇列同步器AQS(AbstractQueuedSynchronizer)
關于AQS在原始碼中有十分具體的解釋

同步器是用來構建鎖和其他同步組件的基礎框架,它的實作主要依賴一個int成員變數來表示同步狀態以及通過一個FIFO佇列構成等待佇列,它的子類必須重寫AQS的幾個protected修飾的用來改變同步狀態的方法,其他方法主要是實作了排隊和阻塞機制,狀態的更新使用getState,setState以及compareAndSetState這三個方法,
子類被推薦定義為自定義同步組件的靜態內部類,同步器自身沒有實作任何同步介面,它僅僅是定義了若干同步狀態的獲取和釋放方法來供自定義同步組件的使用,同步器既支持獨占式獲取同步狀態,也支持共享式獲取同步狀態,這樣就可以方便的實作不同型別的同步組件,
同步器是實作鎖(也可以是任意同步組件)的關鍵,在鎖的實作中聚合同步器,利用同步器實作鎖的語意,可以這樣理解二者的關系:鎖是面向使用者,它定義了使用者與鎖互動的介面,隱藏了實作細節;同步器是面向鎖的實作者,它簡化了鎖的實作方式,屏蔽了同步狀態的管理,執行緒的排隊,等待和喚醒等底層操作,鎖和同步器很好的隔離了使用者和實作者所需要關注的領域,
五、AQS的設計模式
AQS的設計模式是使用模板方法設計模式,它將一些方法開放給子類進行重寫,而同步器給同步組件所提供模板方法又會重新被子類重寫的方法,
舉個例子,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();
}
會呼叫tryAcquire方法,而此時當繼承AQS的NonfairSync呼叫模板方法acquire時就會呼叫已經被NonfairSync重寫的tryAcquire方法,這就是使用AQS的方式,
同步組件(這里不僅僅指鎖,還包括CountDownLatch等)的實作依賴于同步器AQS,在同步組件實作中,使用AQS的方式被推薦定義繼承AQS的靜態內部類;
AQS采用模板方法進行設計,AQS的protected修飾的方法需要由繼承AQS的子類進行重寫實作,當呼叫AQS的子類的方法是就會呼叫被重寫的方法;
AQS負責同步狀態的管理,執行緒的排隊,等待和喚醒這些底層操作,而Lock等同步組件主要專注于實作同步語意;
在重寫AQS的方式時,使用AQS提供的getState(),setState()以及compareAndSetState()方法進行修改同步狀態.
AQS可重寫的方法如下圖:

在實作同步組件是AQS提供的模板方法如下圖

AQS提供的模板方法可以分為3類:
獨占式獲取與釋放同步狀態
共享式獲取與釋放同步狀態
查詢同步佇列中等待執行緒情況
同步組件通過AQS提供的模板方法實作自己的同步語意,
六、AQS的使用example
class Mutex implements Lock, java.io.Serializable {
// Our internal helper class
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();
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));
}
}
package passtra;
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();
}
}
}
這個例子來源于AQS原始碼的example,執行情況:

上面的例子實作了獨占鎖的語意,在同一個時刻只允許一個執行緒占有鎖, MutextDemo新建10個執行緒,分別睡眠3s,從執行情況也可以看出當前Thread-6正在執行占有鎖而其他執行緒7/8處于WAIT狀態,按照推薦的方式,Mutext定義了一個集成AQS的靜態內部類Sync,并且重寫了AQS的tryAcquire等等方法,而對state的更新也是利用了getState,setState,compareAndSetStaste這三個方法,在實作lock介面中方法也是呼叫AQS提供的模板方法(因為Sync繼承了AQS),
從這個例子就可以很清楚的看出來,在同步組件的實作上主要利用了AQS,而AQS“屏蔽”了同步狀態的修改,執行緒排隊等底層實作,通過AQS的模板方法可以很方便的給同步組件的實作警醒呼叫,而針對用戶來說,只需要呼叫同步組件提供的方法來實作并發編程即可,同時在新建一個同步組件時需要把握的兩個關鍵點是:
實作同步組件時推薦定義繼承AQS的靜態內部類,并重寫需要的proyected修飾的方法;
同步組件語意的實作依賴于AQS的模板方法,而AQS模板方法有依賴于AQS的子類重寫的方法,
通俗的說,因為AQS整體設計思路采用模板方法設計模式,同步組件以及AQS的功能實際上分別切換成各自的兩部分:
同步組件實作者的角度:
通過可重新寫的方法:
獨占式:
tryAcquire()(獨占式獲取同步狀態);
tryRelease()(獨占式釋放同步狀態);
共享式:
tryAcquireShared()(共享式獲取同步狀態)
tryReleaseShared()(共享式釋放同步狀態)
告訴AQS怎么判斷當前同步狀態是否成功獲取或者是否成功釋放,
同步組件專注于對當前同步狀態的邏輯判斷,從而實作自己的同步語意,這句話比較抽象,舉個例子,上面的Mutex例子中通過tryAcquire方法實作自己的同步語意,在該方法中如果當前同步狀態為0(即該同步組件沒有被任何執行緒獲取),當前執行緒可以獲取同時將狀態更改為1回傳true,否則,該組件只能在同一時刻被執行緒占用,mutex專注于獲取釋放的邏輯來實作自己想要表達的同步語意,
AQS角度
而對AQS來說,只需要同步組件回傳的true和false即可,因為AQS會對true和fals會有不同的操作,true會認為當前執行緒獲取同步組件成功直接回傳,而false的話AQS會將當前獻策還給你插入同步佇列等一系列的方法,
總的來說,同步組件通過重寫AQS的方法實作自己想要表達的同步語意,而AQS只需要同步組件表達true和false即可,AQS會針對true和false不同的情況做不同的處理,
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/55071.html
標籤:Java
