隊列同步器AQS的相關學習理解

本文章主要說明隊列同步器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;
	}
	
}

 

 

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章