天天看點

AbstractQueuedSynchronizer(AQS)源碼解析一

這章不會對Condition部分進行講解

抽象的隊列同步器 AQS

對鎖資源擷取的過程進行了抽象,具體的擷取過程留給子類去實作

子類不用去關心在擷取不到鎖資源的情況下,目前線程如何被挂起,如何被喚醒

這些都是由AQS來實作,擷取資源和釋放資源是典型的模闆方式模式應用

AQS的重要屬性

AbstractQueuedSynchronizer(AQS)源碼解析一

雙向連結清單來控制鎖資源的擷取

頭節點是擷取到鎖的線程,thread會被置為null

AbstractQueuedSynchronizer(AQS)源碼解析一

節點資訊

節點的兩種模式:共享、獨占

節點的五種狀态:初始化狀态、CANCELLED、SIGNAL、CONDITION、PROPAGATE

CONDITION、PROPAGATE隻會在共享模式出現

AbstractQueuedSynchronizer(AQS)源碼解析一

了解了上面的資訊後,我們直接進入今天的重點。

AQS擷取獨占鎖 不可中斷的

/**
     *  擷取獨占鎖   
     *  tryAcquire 留給需要的子類實作  模闆方法模式
     * @param arg 需要的資源數量
     */
    public final void acquire(int arg) {
        //tryAcquire    嘗試擷取失敗 
        //addWaiter 添加獨占模式的節點到線程同步隊列
        //acquireQueued  會傳回目前線程是否需要被标記為中斷的  true 将目前線程标記為中斷的 
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            //設定目前線程中斷的标志位為true
            selfInterrupt();
    }

	 /**
     * 	嘗試擷取獨占鎖
     * 	具體政策由子類實作
     * @return true 擷取成功 false 擷取失敗
     */
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
           

tryAcquire方法是由具體的鎖實作對資源的擷取,AQS中protected是讓子類有選擇的去實作,如果用Abstract子類就必須要去實作它了,不然自己也成了抽象類了。

當tryAcquire擷取資源失敗了,後續怎麼處理呢?别慌,AQS都幫我們實作好了,讓我們不用關心,這就是AQS出現的理由。

我們來看看AQS是怎麼處理。

當自己實作的tryAcquire擷取獨占鎖失敗之後,addWaiter(Node.EXCLUSIVE),會保證将獨占模式的節點加入到阻塞隊列中。

AbstractQueuedSynchronizer(AQS)源碼解析一

可以看到死循環(自旋)、CAS用到的很多

acquireQueued

1.如果前驅是頭節點就再調用tryAcquire,嘗試擷取一下鎖

2.node是不是需要被挂起且挂起目前線程并傳回線程的中斷狀态

3.繼續循環

AbstractQueuedSynchronizer(AQS)源碼解析一

shouldParkAfterFailedAcquire(p, node),判斷node擷取鎖失敗後是不是被挂起。

AbstractQueuedSynchronizer(AQS)源碼解析一

parkAndCheckInterrupt,挂起目前線程(等待被喚醒)并且會傳回線程的中斷狀态

/**
     *	 挂起目前線程(等待被喚醒)
     *	 會傳回線程的中斷狀态
     *	 @return true 目前線程被标記中斷的 false 沒有被标記中斷的
     */
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        //傳回目前的線程中斷位标志 如果是true的話 會将中斷标志重置為false
        return Thread.interrupted();
    }
           

這裡要注意一下,為什麼方法會傳回中斷狀态呢?,是因為有些擷取鎖的方法是可以被中斷的。

擷取共享鎖

/**
     *	擷取共享鎖
     */
    public final void acquireShared(int arg) {
    	//由子類實作 小于0說明擷取失敗
        if (tryAcquireShared(arg) < 0)
        	//擷取共享鎖 AQS實作
            doAcquireShared(arg);
    }
    /**
     * 	嘗試擷取共享鎖
     * 	具體政策由子類實作
     * @return 傳回剩餘的可用資源數
     */
    protected int tryAcquireShared(int arg) {
        throw new UnsupportedOperationException();
    }
           

都是一樣的套路,留給子類實作。

doAcquireShared

1.*addWaiter前面已經說過了,隻是這裡添加的是共享模式的節點

2. 如果是前驅是頭節點的話,就嘗試擷取共享鎖,也是調用子類的tryAcquireShared,擷取完之後,如果還有可用的資源,就向後傳播,并喚醒其它等待鎖的節點

3. node是不是需要被挂起且挂起目前線程并傳回線程的中斷狀态

4. 繼續循環

AbstractQueuedSynchronizer(AQS)源碼解析一

setHeadAndPropagate,把node設定同步隊列的頭節點,如果還有可用的資源數,則繼續喚醒後續節點

AbstractQueuedSynchronizer(AQS)源碼解析一

shouldParkAfterFailedAcquire和parkAndCheckInterrupt**擷取獨占鎖那裡有可以看下。

獨占鎖和共享鎖的擷取已經說了現在再來說說釋放

釋放獨占鎖

/**
     * 	釋放獨占鎖
     * @return true 釋放成功 false 釋放失敗
     */
    public final boolean release(int arg) {
    	//嘗試釋放獨占鎖 留給需要的子類實作
        if (tryRelease(arg)) {
        	//獨占鎖釋放成功 喚醒後繼節點
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	//喚醒node的後繼節點
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    /**
     * 	嘗試釋放獨占鎖
     * 	具體政策由子類實作
     * @return true 擷取成功 false 擷取失敗
     */
    protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }
           

tryRelease,由子類實作。

unparkSuccessor,喚醒node的後繼節點。

AbstractQueuedSynchronizer(AQS)源碼解析一

釋放共享鎖

/**
     * 	釋放共享鎖
     */
    public final boolean releaseShared(int arg) {
    	//嘗試釋放共享鎖成功 子類實作
        if (tryReleaseShared(arg)) {
            //喚醒後繼阻塞節點
        	doReleaseShared();
            return true;
        }
        return false;
    }
           

doReleaseShared

AbstractQueuedSynchronizer(AQS)源碼解析一

總結

上面說的擷取鎖和釋放鎖都隻是其中的一種方式,AQS還提供了,可中斷、可逾時的擷取和釋放鎖的方式,關于AbstractQueuedSynchronizer完整的源碼解析點選這裡。後續我會用JUC裡面提供的一系列并發工具類來更具體的說明AQS。

繼續閱讀