線程協作工具類原理(2):AQS


1 學習AQS的思路

  • 學習AQS的目的主要是想理解原理、提高技術,以及應對面試
  • 先從應用層面理解爲什麼需要他如何使用它,然後再看一看我們Java代碼的設計者是如何使用它的瞭解它的應用場景
  • 這樣之後我們再去分析它的結構,這樣的話我們就學習得更加輕鬆了

2 爲什麼需要AQS?

鎖和協作類有共同點:閘門

  • 我們已經學過了ReentrantLock和Semaphore,有沒有發現它們有共同點?很相似?
  • 事實上,不僅是ReentrantLock和Semaphore,包括CountDownLatch、ReentrantReadWriteLock都有這樣類似的 “協作”(或者叫“同步”)功能,其實,它們底層都用了一個共同的基類,這就是AQS
  • 因爲上面的那些協作類,它們有很多工作都是類似的,所以如果能提取出一個工具類,那麼就可以直接用,對於ReentrantLock和Semaphore而言就可以屏蔽很多細節,只關注它們自己的 “業務邏輯” 就可以了
  • Semaphore內部有一個Sync類,Sync類繼承了AQS
  • CountDownLatch / ReentrantLock也是一樣的

 

public class Semaphore{
    abstract static class Sync extends AbstractQueuedSynchronizer {
public class CountDownLatch {
    private static final class Sync extends AbstractQueuedSynchronizer {
public class ReentrantLock implements Lock {
    abstract static class Sync extends AbstractQueuedSynchronizer {

AQS的比喻


  • 比喻:羣面、單面
  • 安排就坐、叫號、先來後到等HR的工作就是AQS做的工作
  • 面試官不會去關心兩個面試者是不是號碼相同衝突了,也不想去管面試者需要一個地方坐着休息,這些都交給HR去做

  • Semaphore:一個人面試完了以後,後一個人才能進來繼續面試
  • CountDownLatch:羣面,等待10人到齊
  • Semaphore、CountDownLatch這些同步工具類,要做的就只是寫下自己的 “要人” 規則。比如是 “出一個,進一個” 或者說 “湊齊10人,一起面試”
  • 剩下的招呼面試者的髒活累活交給AQS來做

如果沒有AQS


3 AQS的作用

  • AQS是一個用於構建鎖、同步器、協作工具類的工具類(框架)。有了AQS以後,更多的協作工具類都可以很方便的被寫出來。
  • 一句話總結:有了AQS,構建線程協作類就容易多了。

4 AQS的重要性、地位

AbstractQueuedSynchronizer是Doug Lea寫的,從JDK1.5加入的一個基於FIFO等待隊列實現的一個用於實現同步器的基礎框架。


5 AQS內部原理解析

AQS最核心的就是三大部分

  • state
  • 控制線程搶鎖和配合的FIFO隊列
  • 期望協作工具類去實現的獲取/釋放等重要方法

5.1 state狀態 

  • 這裏的state的具體含義,會根據具體實現類的不同而不同,比如在Semaphore裏,它表示 “剩餘的許可證的數量” ,而在CountDownLatch裏,它表示 “還需要倒數的數量”。
  • state是volatile修飾的,會被併發地修改,所以所有修改state的方法都需要保證線程安全,比如getState、setState以及compareAndSetState操作來讀取和更新這個狀態。這些方法都依賴於java.util.concurrent包的支持。
  • ReentrantLock
  • state用來表示 “鎖” 的佔有情況,包括可重入計數
  • 當state的值爲0的時候,標識該Lock不被任何線程所佔有
//java.util.concurrent.locks.AbstractQueuedSynchronizer#state
private volatile int state;

5.2 控制線程搶鎖和配合的FIFO隊列

  • 這個隊列用來存放 “等待的線程” ,AQS就是 “排隊管理器” 當多個線程爭用同一把鎖時,必須有排隊機制將那些沒能拿到鎖的線程串在一起。當鎖釋放時,鎖管理器就會挑選一個合適的線程來佔有這個剛剛釋放的鎖
  • AQS會維護一個等待的線程隊列,把線程都放到這個隊列裏
  • 這是一個雙向形式的隊列

5.3 期望協作工具類去實現獲取/釋放等重要方法 

這裏的獲取和釋放方法,是利用AQS的協作工具類裏最重要的方法,是由協作類自己去實現的,並且含義各不相同


獲取方法

  • 獲取操作會依賴state變量,經常會阻塞(比如獲取不到鎖的時候)
  • Semaphore中,獲取就是acquire方法,作用是獲取一個許可證
  • 而在CountDownLatch裏面,獲取就是await方法,作用是 “等待,直到倒數結束

釋放方法

  • 釋放操作不會阻塞
  • 在Semaphore中,釋放就是release方法,作用是釋放一個許可證
  • CountDownLatch裏面,獲取就是countDown方法,作用是 “倒數1個數

5.4 需要重寫tryAcquire和tryRelease等方法 

//java.util.concurrent.CountDownLatch.Sync
private static final class Sync extends AbstractQueuedSynchronizer {
	protected int tryAcquireShared(int acquires) {
		//...
	}
	protected boolean tryReleaseShared(int releases) {
		//...
	}
}

6 應用實例、源碼解析

6.1 AQS用法

  • 第一步:寫一個類,想好協作的邏輯,實現獲取/釋放方法
  • 第二步:內部寫一個Sync類繼承AbstractQueuedSynchronizer
  • 第三步:根據是否獨佔來重寫 tryAcquire/tryRelease或tryAcquireShared(int acquires)和tryReleaseShared(int releases)等方法,在之前寫的獲取/釋放方法中調用AQS的acquire/release或者Shared方法

6.2 AQS在CountDownLatch的應用

構造函數

//java.util.concurrent.CountDownLatch#CountDownLatch
public CountDownLatch(int count) {
	if (count < 0) throw new IllegalArgumentException("count < 0");
	this.sync = new Sync(count);
}
//java.util.concurrent.CountDownLatch.Sync#Sync
Sync(int count) {
	setState(count);
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer#setState
protected final void setState(int newState) {
	state = newState;
}

getCount

//java.util.concurrent.CountDownLatch#getCount
public long getCount() {
	return sync.getCount();
}
//java.util.concurrent.CountDownLatch.Sync#getCount
int getCount() {
	return getState();
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer#getState
protected final int getState() {
	return state;
}

countDown

  • 執行-1操作
  • 當減到0的時候喚醒所有等待的線程
//java.util.concurrent.CountDownLatch#countDown
public void countDown() {
	sync.releaseShared(1);
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer#releaseShared
public final boolean releaseShared(int arg) {
	if (tryReleaseShared(arg)) {
		//喚醒之前陷入等待的線程
		doReleaseShared();
		return true;
	}
	return false;
}
//java.util.concurrent.CountDownLatch.Sync#tryReleaseShared
protected boolean tryReleaseShared(int releases) {
	// Decrement count; signal when transition to zero
	//自旋
	for (;;) {
		int c = getState();
		//已經釋放過
		if (c == 0)
			return false;
		//
		int nextc = c-1;
		//CAS更新
		if (compareAndSetState(c, nextc))
			//返回true:喚醒等待線程
			return nextc == 0;
	}
}

await

  • 判斷當前的線程是否需要等待
  • state > 0:還沒有倒數結束,讓當前線程進入阻塞隊列中
  • state = 0:倒數結束,直接放行
//java.util.concurrent.CountDownLatch#await()
public void await() throws InterruptedException {
	sync.acquireSharedInterruptibly(1);
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
	//判斷中斷
	if (Thread.interrupted()){
		throw new InterruptedException();
	}
	if (tryAcquireShared(arg) < 0){
		//當前線程進入等待隊列
		doAcquireSharedInterruptibly(arg);
	}
}
//java.util.concurrent.CountDownLatch.Sync#tryAcquireShared
protected int tryAcquireShared(int acquires) {
	//state==0:等待隊列裏面的線程都可以放行了
	//state>0:倒數還未結束,線程需要放入等待隊列等待
	return (getState() == 0) ? 1 : -1;
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer#doAcquireSharedInterruptibly
//把當前線程放入阻塞隊列,並且把線程陷入阻塞狀態
private void doAcquireSharedInterruptibly(int arg)throws InterruptedException {
	//把當前線程包裝成一個Node節點(參考上面的圖片)
	final Node node = addWaiter(Node.SHARED);
	boolean failed = true;
	try {
		for (;;) {
			final Node p = node.predecessor();
			if (p == head) {
				int r = tryAcquireShared(arg);
				if (r >= 0) {
					setHeadAndPropagate(node, r);
					p.next = null; // help GC
					failed = false;
					return;
				}
			}
			if (shouldParkAfterFailedAcquire(p, node) &&
				//
				parkAndCheckInterrupt())
				throw new InterruptedException();
		}
	} finally {
		if (failed)
			cancelAcquire(node);
	}
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer#parkAndCheckInterrupt
//掛起當前線程,進入阻塞狀態
private final boolean parkAndCheckInterrupt() {
	LockSupport.park(this);
	return Thread.interrupted();
}
//java.util.concurrent.locks.LockSupport#park(java.lang.Object)
public static void park(Object blocker) {
	Thread t = Thread.currentThread();
	setBlocker(t, blocker);
	UNSAFE.park(false, 0L);
	setBlocker(t, null);
}
//sun.misc.Unsafe#park
public native void park(boolean var1, long var2);

AQS在CountDownLatch的總結

  • 調用CountDownLatch的await方法時,便會嘗試獲取 “共享鎖” ,不過一開始是獲取不到該鎖的,於是線程被阻塞
  • 而 “共享鎖” 可獲取到的條件,就是 “鎖計數器” 的值爲0
  • 而 “鎖計數器” 的初始值爲count,每當一個線程調用該CountDownLatch對象的countDown()方法時,纔將 “鎖計數器” -1
  • count個線程調用countDown()之後,“鎖計數器” 才爲0,而前面提到的等待獲取共享鎖的線程才能繼續運行

6.3 AQS在Semaphore的應用

  • 在Semaphore中,state表示許可證的剩餘數量
  • 看tryAcquire方法,判斷nonfairTryAcquireShared大於等於0的話,代表成功
  • 這裏會先檢查剩餘許可證數量夠不夠這次需要的,用減法來計算,如果直接不夠,那就返回負數,表示失敗,如果夠了,就用自旋加compareAndSetState來改變state狀態,直到改變成功就返回正數;或者是期間如果被其他人修改了導致剩餘數量不夠了,那也返回負數代表獲取失敗
//java.util.concurrent.Semaphore#acquire(int)
public void acquire(int permits) throws InterruptedException {
	if (permits < 0) throw new IllegalArgumentException();
	sync.acquireSharedInterruptibly(permits);
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer#acquireSharedInterruptibly
public final void acquireSharedInterruptibly(int arg)throws InterruptedException {
	if (Thread.interrupted()){
		throw new InterruptedException();
	}
	if (tryAcquireShared(arg) < 0){
		//當前線程進入排隊等待的狀態
		doAcquireSharedInterruptibly(arg);
	}
}
//java.util.concurrent.Semaphore.NonfairSync#tryAcquireShared        
protected int tryAcquireShared(int acquires) {
	return nonfairTryAcquireShared(acquires);
}
//java.util.concurrent.Semaphore.Sync#nonfairTryAcquireShared
//負數:獲取失敗
//正數:獲取成功
final int nonfairTryAcquireShared(int acquires) {
	for (;;) {
		//當前剩餘的許可證的數量
		int available = getState();
		//計算夠不夠
		int remaining = available - acquires;
		if (remaining < 0 ||
			//CAS修改剩餘的許可證數量
			compareAndSetState(available, remaining))
			return remaining;
	}
}

6.4 AQS在ReentrantLock的應用

分析釋放鎖的方法tryRelease

  • 由於是可重入的,所以state代表重入的次數,每次釋放鎖,先判斷是不是當前持有鎖的線程釋放的,如果不是就拋異常,如果是的話,重入次數就減一,如果減到了0,就說明完全釋放了,於是free就是true,並且把state設置爲0。

加鎖的方法

  • 判斷當前state是否等於0當前線程是否是持有鎖的線程。都不是就拿不到這把鎖,然後把當前線程放入隊列等待以後在合適的時候喚醒

分析釋放鎖的方法tryRelease

//java.util.concurrent.locks.ReentrantLock#unlock
public void unlock() {
	sync.release(1);
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer#release
public final boolean release(int arg) {
	if (tryRelease(arg)) {
		//已經釋放了鎖(喚醒等待的線程)
		Node h = head;
		if (h != null && h.waitStatus != 0)
			unparkSuccessor(h);
		return true;
	}
	return false;
}
//java.util.concurrent.locks.ReentrantLock.Sync#tryRelease
protected final boolean tryRelease(int releases) {
	//重入次數-1
	int c = getState() - releases;
	//判斷當前線程是否是持有鎖的線程
	if (Thread.currentThread() != getExclusiveOwnerThread())
		throw new IllegalMonitorStateException();
	boolean free = false;
	if (c == 0) {
		//設置這把鎖自由
		free = true;
		//設置當前持有這把鎖的線程爲null
		setExclusiveOwnerThread(null);
	}
	//更新
	setState(c);
	return free;
}
//java.util.concurrent.locks.ReentrantLock#lock
public void lock() {
	sync.lock();
}
//java.util.concurrent.locks.ReentrantLock.NonfairSync#lock
final void lock() {
	if (compareAndSetState(0, 1)){//CAS:當前沒有任何線程持有這把鎖的時候才能夠設置成功
		setExclusiveOwnerThread(Thread.currentThread());//當前線程設置爲持有鎖的線程
	}else{
		acquire(1);
	}	
}
//java.util.concurrent.locks.AbstractQueuedSynchronizer#acquire
public final void acquire(int arg) {
	//獲取鎖失敗(!tryAcquire(arg) == true)當前線程會被放入等待隊列中(addWaiter(Node.EXCLUSIVE)),並且去排隊直到時機合適再來重新獲取鎖
	if (!tryAcquire(arg) && acquireQueued(addWaiter(Node.EXCLUSIVE), arg)){
		selfInterrupt();
	}	
}
//java.util.concurrent.locks.ReentrantLock.NonfairSync#tryAcquire
protected final boolean tryAcquire(int acquires) {
	return nonfairTryAcquire(acquires);
}
//java.util.concurrent.locks.ReentrantLock.Sync#nonfairTryAcquire
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()) {//這把鎖的持有者是當前線程(重入)
		//重入次數+1(acquires通常是1)
		int nextc = c + acquires;
		if (nextc < 0) // overflow
			throw new Error("Maximum lock count exceeded");
		//重入次數+1	
		setState(nextc);
		return true;
	}
	return false;
}

7 利用AQS實現一個自己的Latch門閂

第一步∶寫一個類,想好協作的邏輯,實現獲取/釋放方法

第二步:內部寫一個Sync類繼承AbstractQueuedSynchronizer

第三步:根據是否獨佔來重寫tryAcquire/tryRelease或tryAcquireShared(int acquires)和tryReleaseShared(int releases)等方法,在之前寫的獲取/釋放方法中調用AQS的acquire/release或者Shared方法

/**
 * 自己用AQS實現一個簡單的線程協作器
 */
public class OneShotLatch {
    private final Sync sync = new Sync();
    //釋放
    public void signal() {
        sync.releaseShared(0);
    }
    //獲取
    public void await() {
        sync.acquireShared(0);
    }
    /**
     * state
     * 0:關閉
     * 1:打開
     */
    private class Sync extends AbstractQueuedSynchronizer {
        @Override
        protected int tryAcquireShared(int arg) {
            return (getState() == 1) ? 1 : -1;
        }
        @Override
        protected boolean tryReleaseShared(int arg) {
           setState(1);
           return true;
        }
    }
    public static void main(String[] args) throws InterruptedException {
        OneShotLatch oneShotLatch = new OneShotLatch();
        for (int i = 0; i < 10; i++) {
            new Thread(() -> {
                System.out.println(Thread.currentThread().getName()+"嘗試獲取latch,獲取失敗那就等待");
                oneShotLatch.await();
                System.out.println("開閘放行"+Thread.currentThread().getName()+"繼續運行");
            }).start();
        }
        Thread.sleep(5000);
        oneShotLatch.signal();

        new Thread(() -> {
            System.out.println(Thread.currentThread().getName()+"嘗試獲取latch,獲取失敗那就等待");
            oneShotLatch.await();
            System.out.println("開閘放行"+Thread.currentThread().getName()+"繼續運行");
        }).start();
    }
}

 

 

發佈了537 篇原創文章 · 獲贊 2350 · 訪問量 204萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章