Java并发框架——AQS之怎样使用AQS构建同步器
AQS的設計思想是通過繼承的方式提供一個模板讓大家能夠非常easy依據不同場景實現一個富有個性化的同步器。同步器的核心是要管理一個共享狀態,通過對狀態的控制即能夠實現不同的鎖機制。
AQS的設計必須考慮把復雜反復且easy出錯的隊列管理工作統一抽象出來管理,而且要統一控制好流程,而暴露給子類調用的方法主要就是操作共享狀態的方法,以此提供對狀態的原子性操作。一般子類的同步器中使用AQS提供的getState、setState、compareAndSetState三個方法,前兩個為普通的get和set方法,要使用這兩個方法必需要保證不存在數據競爭,compareAndSetState方法提供了CAS方式的硬件級別的原子更新。
對于獨占模式而言。鎖獲取與釋放的流程的定義則交給acquire和release兩個方法,它們定義了鎖獲取與釋放的邏輯。同一時候也是提供給子類獲取和釋放鎖的接口。它的運行邏輯能夠參考前面的“鎖的獲取與釋放”。它提供了一個如何強大的模板?由以下的偽代碼能夠清晰展示出來。請注意tryAcquire和tryRelease這兩個方法,它就是留給子類實現個性化的方法,通過這兩個方法對共享狀態的管理能夠自己定義多種多樣的同步器。而隊列的管理及流程的控制則不是你需要考慮的問題。
① 鎖獲取模板
if(tryAcquire(arg)) {
? ? 創建node
? ? 使用CAS方式把node插入到隊列尾部
? ? while(true){
? ? if(tryAcquire(arg) 而且 node的前驅節點為頭節點){
把當前節點設置為頭節點
? ? 跳出循環
}else{
? ? 使用CAS方式改動node前驅節點的waitStatus標識為signal
? ? if(改動成功)
? ? ? ? 掛起當前線程?
}
}
② 鎖釋放模板
? ? if(tryRelease(arg)){
喚醒興許節點包括的線程
}
我們能夠覺得同步器可實現不論什么不同鎖的語義。一般提供給使用者的鎖是用AQS框架封裝實現的更高層次的實現,提供一種更加形象的API讓使用者使用起來更加方便簡潔。而不是讓使用者直接接觸AQS框架,比如。ReentrantLock、Semphore、CountDownLatch等等。這些不同的形象的鎖讓你使用起來更好理解更加得心應手,并且不easy混淆。
然而這些鎖都是由AQS實現。AQS同步器面向的是線程和狀態的控制,定義了線程獲取狀態的機制及線程排隊等操作,非常好地隔離了兩者的關注點,高層關注的是場景的使用,而AQS同步器則關心的是并發的控制。
假如你要實現一個自己定義同步裝置,官方推薦的做法是將集成AQS同步器的子類作為同步裝置的內部類,而同步裝置中相關的操作僅僅需代理成子類中相應的方法就可以。往下用一個簡單的樣例看看怎樣實現自己的鎖,因為同步器被分為兩種模式。獨占模式和共享模式。所以樣例也相應給出。
① 獨占模式。獨占模式採取的樣例是銀行服務窗體,假如某個銀行網點僅僅有一個服務窗體,那么此銀行服務窗體僅僅能同一時候服務一個人。其它人必須排隊等待,所以這樣的銀行窗體同步裝置是一個獨占模型。
第一個類是銀行窗體同步裝置類。它依照推薦的做法使用一個繼承AQS同步器的子類實現,并作為子類出現。第二個類是測試類,形象一點地說。有三位良民到銀行去辦理業務,各自是tom、jim和jay,我們使用BankServiceWindow就能夠約束他們排隊,一個一個輪著辦理業務而避免陷入混亂的局面。
public class BankServiceWindow {
private final Sync sync;
public BankServiceWindow() {
sync = new Sync();
}
private static class Sync extends AbstractQueuedSynchronizer {
public boolean tryAcquire(int acquires) {
if (compareAndSetState(0, 1)) {
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
protected boolean tryRelease(int releases) {
if (getState() == 0)
throw new IllegalMonitorStateException();
setExclusiveOwnerThread(null);
setState(0);
return true;
}
}
public void handle() {
sync.acquire(1);
}
public void unhandle() {
sync.release(1);
}
}
public class BankServiceWindowTest {
? ?public static void main(String[] args){
? ?final BankServiceWindow bankServiceWindow=new BankServiceWindow();
? ?Thread tom=new Thread(){
? ?public void run(){
? ?bankServiceWindow.handle();
? ?System.out.println("tom開始辦理業務");
? ?try {
? ?this.sleep(5000);
? ?} catch (InterruptedException e) {
? ?e.printStackTrace();
? ?}
? ?System.out.println("tom結束辦理業務");
? ?bankServiceWindow.unhandle();
? ?}
? ?};
? ?Thread jim=new Thread(){
? ?public void run(){
? ?bankServiceWindow.handle();
? ?System.out.println("jim開始辦理業務");
? ?try {
? ?this.sleep(5000);
? ?} catch (InterruptedException e) {
? ?e.printStackTrace();
? ?}
? ?System.out.println("jim結束辦理業務");
? ?bankServiceWindow.unhandle();
? ?}
? ?};
? ?Thread jay=new Thread(){
? ?public void run(){
? ?bankServiceWindow.handle();
? ?System.out.println("jay開始辦理業務");
? ?try {
? ?this.sleep(5000);
? ?} catch (InterruptedException e) {
? ?e.printStackTrace();
? ?}
? ?System.out.println("jay結束辦理業務");
? ?bankServiceWindow.unhandle();
? ?}
? ?};
? ?tom.start();
? ?jim.start();
? ?jay.start();
? ? }
}
輸出結果例如以下:
tom開始辦理業務
tom結束辦理業務
jim開始辦理業務
jim結束辦理業務
jay開始辦理業務
jay結束辦理業務
明顯tom、jim、jay仨是排隊完畢的。可是無法保證三者的順序,可能是tom、jim、jay,也可能是tom、jay、jim。由于在入列曾經的運行先后是無法確定的,它的語義是保證一個接一個辦理。
假設沒有同步器限制的情況。輸出結果將不可預測。可能為輸出例如以下:
jim開始辦理業務
jay開始辦理業務
tom開始辦理業務
jay結束辦理業務
jim結束辦理業務
tom結束辦理業務
② 共享模式,共享模式採取的樣例相同是銀行服務窗體,隨著此網點的發展。辦理業務的人越來越多,一個服務窗體已經無法滿足需求,于是又分配了一位員工開了另外一個服務窗體,這時就能夠同一時候服務兩個人了,但兩個窗體都有人占用時相同也必須排隊等待,這樣的服務窗體同步器裝置就是一個共享型。第一個類是共享模式的同步裝置類,跟獨占模式不同的是它的狀態的初始值能夠自由定義,獲取與釋放就是對狀態遞減和累加操作。第二個類是測試類,tom、jim和jay再次來到銀行,一個有兩個窗體甚是高興,他們能夠兩個人同一時候辦理了,時間縮減了不少。
public class BankServiceWindows {
private final Sync sync;
public BankServiceWindows(int count) {
sync = new Sync(count);
}
private static class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
public int tryAcquireShared(int interval) {
for (;;) {
int current = getState();
int newCount = current - 1;
if (newCount < 0 || compareAndSetState(current, newCount)) {
return newCount;
}
}
}
public boolean tryReleaseShared(int interval) {
for (;;) {
int current = getState();
int newCount = current + 1;
if (compareAndSetState(current, newCount)) {
return true;
}
}
}
}
public void handle() {
sync.acquireShared(1);
}
public void unhandle() {
sync.releaseShared(1);
}
}
public class BankServiceWindowsTest {
public static void main(String[] args){
final BankServiceWindows bankServiceWindows=new BankServiceWindows(2);
Thread tom=new Thread(){
public void run(){
bankServiceWindows.handle();
System.out.println("tom開始辦理業務");
try {
this.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("tom結束辦理業務");
bankServiceWindows.unhandle();
}
};
Thread jim=new Thread(){
public void run(){
bankServiceWindows.handle();
System.out.println("jim開始辦理業務");
try {
this.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("jim結束辦理業務");
bankServiceWindows.unhandle();
}
};
Thread jay=new Thread(){
public void run(){
bankServiceWindows.handle();
System.out.println("jay開始辦理業務");
try {
this.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("jay結束辦理業務");
bankServiceWindows.unhandle();
}
};
tom.start();
jim.start();
jay.start();
}
}
可能的輸出結果為:
tom開始辦理業務
jay開始辦理業務
jay結束辦理業務
tom結束辦理業務
jim開始辦理業務
jim結束辦理業務
tom和jay差點兒同一時候開始辦理業務,而jay結束后有空暇的服務窗體jim才過來。
這節主要講的是怎樣使用AQS構建自己的同步器。而且剖析了鎖獲取與釋放的模板的邏輯,讓你更好理解AQS的實現,最后分別給出了獨占模式和共享模式的同步器實現的樣例。相信你們搞清楚這兩種方式的實現后。要構建更加復雜的同步器就知道力往哪里使了。
喜歡研究java的同學能夠交個朋友。以下是本人的微信號:
總結
以上是生活随笔為你收集整理的Java并发框架——AQS之怎样使用AQS构建同步器的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 算法笔记_156:算法提高 6-17复数
- 下一篇: 百度2016/2017秋招部分题目解析