本文章主要說明隊列同步器AbstractQueuSynchronized的使用,以及對其主要方法的簡單分析。
隊列同步器(以下簡稱AQS)
隊列同步器,是用來構建鎖或者其他同步組件的基礎框架,它使用了一個int成員變量來表示同步狀態,通過內置的FIFO隊列來完成狀態搶佔線程的排隊工作。
同步狀態的表示和操作
很明顯,表示同步狀態的這個量爲int類型,且該變量是volatile修飾的,保證可見性!
那既然涉及到操作內部的int成員變量,那一定有相應的操作方法:我們來看看源碼:
使用CAS設置當前狀態,該方法能夠保證狀態設置的原子性
compareAndSetState(expect, update)
看到這裏,你應該知道,同步隊列內部維護着一個int變量,且有幾個方法可以操作這個變量。
後續你會理解,這個int變量,所起到的作用,也就是所謂的狀態。
AQS的具體使用
關於其使用,我們先來思考這樣一個問題,如果我們現在要實現一個獨佔鎖,也就是自定義一個類,該類具有獨佔鎖的功能。所謂的獨佔鎖,就是同一時刻,只能有最多一個線程獲取到該鎖。我們的基本思路是,該自定義類維護一個標記變量,如果有線程獲取到這個標記,我們就改變標記位爲不可獲取狀態,保證其他線程無法再獲取,當該線程使用完鎖後,釋放鎖,也就是把標記位再改爲可獲取狀態。
對的,你沒有想錯,AQS就是幹這個事情的。他已經實現了上述的邏輯,所以,我們只需要在我們自定義同步器中使用到AQS就可以了,只不過他其中有幾個方法需要我們重寫,來滿足我們想要具體實現的邏輯。這很好理解,如果我們自定義同步器,實現的是獨佔鎖,我們使用AQS,然後重寫其中要用的方法,讓其實現邏輯爲獨佔鎖就可以了。
說了這麼多,相信你也沒看懂,哈哈!我們上代碼來消化這些理論文字!
上述代碼中出現了這麼幾個角色:
1.LockTest:正如註釋中所說的那樣,我們的自定義類,實現的功能爲一個獨佔鎖的功能(這代碼並不全,LockTest類還有很多方法,是實現自Lock接口的,下面會分析)。該類,實現了Lock接口。
2.MyAQS:自定義同步隊列器,繼承自AQS。是LockTest我們自定義類的靜態內部類。這樣定義的好處是,私有封裝。
然後看代碼所示的兩個方法,就是我們MyAQS繼承自AQS所重寫的兩個方法,其中tryAcquire()方法,就是嘗試獲取一下AQS內部維護的狀態,而tryRelease()方法,則就代表的是釋放狀態,也就是釋放鎖。這時候,我們的自定義同步器其實就做好了。
再來看LockTest剩餘的代碼:
好了,我們可以在我們的自定義類LockTest中使用自定義邏輯的同步器了,正如第一行代碼,內部維護這一個myAQS。好了,下面就是自定義類實現接口Lock的具體方法了,先來看一下圖中的lock()方法,他調用了myAQS.acquire()方法,很顯然,我們自定義AQs中並沒有該方法,那顯然是來自繼承的AQS了,我們看jdk源碼:
哎,我們發現,acquire中竟然使用到了我們重寫的tryAcquire()方法,仔細想一下,立刻就明白了,也就是,我們自定義類的lock方法,其實是調用繼承自底層AQS的請求方法,然後AQS再去調用我們自定義AQS中重寫的tryAcquire().再回到代碼裏,發現如果獲取狀態失敗,怎麼辦呢?也就是tryAcquire返回false,看圖中if中的判斷條件是!tryAcquire,也就是返回false的時候,if中的第一個判斷邏輯其實是true,因爲是&&,繼續判斷第二個邏輯,也就是第三行,而這一步,就是把請求線程放到了AQS所維護的請求等待隊列中了。
那麼整個使用AQS的邏輯就出來了,如下總結:
現在我們要做一個自定義類實現同步組件的功能,涉及四個角色,以上述例子中的名字做分析:
四個角色:
1.我們自定義的實現類,LockTest,其實現Lock接口,實現lock中基本的鎖方法
2.Lock接口
3.我們自定義的隊列同步器,作爲LockTest的靜態內部類,繼承AQS,實現具體的鎖邏輯
4.AQS,被自定義同步器繼承
那麼,細心的你,在理解了上述討論後,會立馬問,什麼方法需要重寫,什麼方法不重寫呢?
我們分別以獨佔鎖和共享鎖爲例進行討論:
1.獨佔鎖,如果我們自定義同步器實現的是獨佔鎖的邏輯,我們在MyAQS中重寫tryAcquire()方法和tryRelease()方法即可,而自定義類,LockTest中的lock()方法中直接調用acquire()方法,unlock()方法中直接調用release()方法。
2.共享鎖,如果我們自定義同步器實現的是共享鎖的邏輯,我們在MyAQS中重寫tryAcquireShared()方法和tryReleaseShared()方法即可,而自定義類LockTest中的lock()方法中直接調用acquireShared()方法,unlock()方法中直接調用releaseShared()方法。
其實這很好理解:我們自定義類實現Lock接口,本質上就是實現lock()方法和unlock()方法,而AQS的acquire(),acquireShared()已經爲我們提供了實現lock()的邏輯模板,而所謂的邏輯模板就做兩件事,一是去執行獲取鎖(管理同步狀態)的邏輯,二是去執行獲取不到鎖的情況下維護一個請求隊列,分析源碼,我們已經知道了,第二件事acquire(),acquireShared()這兩個方法已經爲我們實現了,這也是我們用AQS的意義,所以我們要自己去做第一件事,也就是去實現tryAcquire(),tryAcquireShared()這兩個方法,所以我們要重寫這兩個方法,達到我們所要實現的請求獲取鎖時候的邏輯。
看到這裏,不得不提出的是,使用AQS其實是基於模板方法的設計模式,也就是AQS內部可以分成兩種方法,一種是類似於模板方法,我們可以直接使用,但是必須實現模板方法中需要重寫的方法。第二種就是可重寫方法,也就是我們自定義組件,實現的自定義邏輯的方法。
例如:tryAcquire(),tryRelease(),tryAcquireShared(),tryReleaseShared()都是可重寫方法
而,acquire(),release(),acquireShared(),releaseShared()都是已經實現的模板方法,去調用可重寫方法
此時我們會發現整個AQS的作用:
AQS是實現鎖(也可以是任意同步組件)的關鍵,在鎖的實現中聚合同步器,利用同步器實現鎖的具體邏輯。可以這樣理解二者之間的關係:鎖是面向使用者的,他定義了使用者與鎖交互的接口,隱藏了實現細節。而同步器面向的是鎖的實現者,它簡化了鎖的實現方式,屏蔽了同步狀態管理,線程的排隊,等待與喚醒等底層的操作。所以,鎖和同步器很好的隔離了使用者和實現者所需要關注的領域。
附上練習代碼:
兩個代碼,一個是獨佔鎖的實現,一個是共享鎖的實現。
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class AQSTest {
//我們來測試一下我們自定義的同步組件LockTest
public static void main(String args[])
{
//我們寫兩個線程來搶佔該鎖
ThreadTest tt = new ThreadTest();
Thread thread1 = new Thread(tt);
Thread thread2 = new Thread(tt);
thread1.start();
thread2.start();
try {
//Join方法,保證兩個子線程運行結束後,繼續執行主線程
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("多線程處理結果:" + tt.num);
}
}
//我們封裝一個線程類
class ThreadTest implements Runnable
{
private Lock lock = new LockTest();
public int num = 0;
public void run() {
lock.lock(); //獲取鎖
try {
for(int i = 0; i < 100000; i++)
num++;
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();//finally中保證鎖一定關閉
}
}
}
//自定義同步組件,也就是自己實現一個獨佔鎖功能的類
class LockTest implements Lock
{
//靜態內部類,自定義同步器
private static class MyAQS extends AbstractQueuedSynchronizer
{
//繼承AQS,就要重寫其可重寫方法,實現自己同步器的邏輯,然後由模板方法調用
//這裏強調一下AQS的原理,底層維護這一個標記狀態,int類型,初始爲0
//然後AQS本身有三個關於這個狀態的操作方法
//getState().setState().compareAndSetState()
//其中第三個方法保證了原子性。
//重寫方法如下:
//判斷是否處於佔用狀態,也就是AQS內部的狀態標記是否被某個線程佔有
//當狀態爲0的時候,獲取鎖
protected boolean tryAcquire(int acquire)
{
if(compareAndSetState(0, 1))
{
//設置該狀態被哪個線程搶佔了
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
//釋放鎖,並把狀態設置爲0
protected boolean tryRelease(int release)
{
if(getState() == 0) //如果狀態爲0,無法釋放
throw new RuntimeException();
//把狀態佔有者設置爲空
setExclusiveOwnerThread(null);
setState(0); //釋放狀態
return true;
}
}
//下面的方法,就是實現lock接口所需要實現的方法
//這時候就是同步隊列器發揮作用的時候了,我們只需要調用其方法就可以了
private final MyAQS myAQS = new MyAQS();
public void lock() {
//調用從AQS繼承的模板方法acquire
//而acquire底層去調用上面重寫的tryAcquire再加上其他的邏輯
myAQS.acquire(1);
}
public boolean tryLock() {
//注意這裏直接調用了我們重寫的方法,因爲tryLock()只會嘗試一次獲取鎖
return myAQS.tryAcquire(1);
}
public void unlock() {
myAQS.release(1);
}
public void lockInterruptibly() throws InterruptedException {
}
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
public Condition newCondition() {
return null;
}
}
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.AbstractQueuedSynchronizer;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
public class AQSTest2 {
//我們來測試一下我們自定義的鎖
public static void main(String args[])
{
ThreadTest2 tt = new ThreadTest2();
Thread thread1 = new Thread(tt);
Thread thread2 = new Thread(tt);
thread1.start();
thread2.start();
try {
thread1.join();
thread2.join();
} catch (Exception e) {
e.printStackTrace();
}
System.out.println("兩個線程的程序運行結果爲:"+tt.number);
}
}
//我們封裝一個線程類
class ThreadTest2 implements Runnable
{
private Lock lock = new LockTest2();
public int number = 0;
public void run()
{
lock.lock();
try {
for(int i = 0; i < 10000; i++)
number++;
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
//實現自定義鎖,實現共享鎖的邏輯
class LockTest2 implements Lock
{
private class MyAQS2 extends AbstractQueuedSynchronizer
{
//構造方法,設置默認狀態數
public MyAQS2(int number)
{
if(number < 0)
throw new RuntimeException();
this.setState(number);
}
//重寫AQS可重寫方法
//共享式狀態的獲取
public int tryAcquireShared(int reduceCount)
{
//死循環,配合下面if中CAS
for(;;)
{
int current = getState();
int newCount = current - reduceCount;
if(newCount < 0 || compareAndSetState(current, newCount))
{
return newCount;
}
}
}
//共享式狀態的釋放
public boolean tryReleaseShared(int addCount)
{
for(;;)
{
int current = getState();
int newCount = current + addCount;
if(newCount < 0 || compareAndSetState(current, newCount))
{
return true;
}
}
}
}
private MyAQS2 myAQS2 = new MyAQS2(2); //設置狀態數爲2,表示最多可以兩個線程共享
@Override
public void lock() {
myAQS2.acquireShared(1);
}
@Override
public void unlock() {
myAQS2.releaseShared(1);
}
@Override
public void lockInterruptibly() throws InterruptedException {
}
@Override
public boolean tryLock() {
return false;
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
return false;
}
@Override
public Condition newCondition() {
return null;
}
}