1. 程式人生 > >Java併發程式設計(七)佇列同步器AQS

Java併發程式設計(七)佇列同步器AQS

一、AQS簡介

佇列同步器AbstractQueuedSynchronizer(簡稱AQS)是用來構建鎖或其他同步元件的基礎框架,它服務的是鎖的實現者。AQS有一個變量表示同步狀態,通過內建的FIFO管理執行緒排隊,基於AQS可以將同步狀態管理、執行緒排隊、等待與喚醒等操作對鎖遮蔽,簡化鎖的實現方式。

同步器的設計是基於模板方法的,使用者需要重寫同步器指定的方法,然後將同步器組合在自定義同步元件的視線中,並呼叫同步器提供的模板方法。AQS的模板方法包括獲取和釋放同步狀態等。

二、AQS原理

理解AQS主要是理解同步佇列管理同步狀態的獲取與釋放兩點。

1、佇列同步器結構

AQS有兩個節點的引用head和tail,分別指向頭尾節點。

入隊設定尾節點:獲取同步狀態(或者說獲取鎖)失敗的執行緒,會被作為節點加入佇列,為保證加入過程的執行緒安全,通過compareAndSetTail方式入隊。(第一次設定頭結點是在這個操作中)

出隊設定頭結點:由於只有一個執行緒能夠成功獲取到同步狀態,因此設定頭結點不需要CAS保證,只需將頭節點的下一個節點設為頭結點。

2、同步狀態的獲取與釋放

獲取過程呼叫方法acquire:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

獲取成功時做兩件事:一是更新同步狀態+1,二是setExclusiveOwnerThread設定獲得同步狀態的執行緒為當前執行緒。

獲取邏輯:通過呼叫同步器方法tryAcquire方法,嘗試獲取同步狀態,如果獲取失敗,則構造同步節點,並CAS呼叫addWaiter方法嘗試將節點加入同步佇列尾部。同步佇列中的節點執行緒呼叫acquireQueued方法,以死迴圈的方式獲取同步狀態。如果獲取不到同步狀態,則阻塞節點中的執行緒,喚醒執行緒的方式是獲取同步狀態成功或執行緒被中斷。獲取過程如下圖:

釋放過程呼叫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;
    }

釋放成功時做兩件事:一是更新同步狀態-1,二是setExclusiveOwnerThread(null)設定獲得同步狀態的執行緒為null。

釋放邏輯:嘗試釋放,如果成功則喚醒後繼節點執行緒。

3、獲取同步狀態的方式

獲取同步狀態的方式有3種:獨佔式獲取同步狀態、共享式獲取同步狀態太、獨佔式超時獲取同步狀態。

三者的區別在於,獨佔式同一時刻只會有一個執行緒獲得到同步狀態,而共享式可以有多個執行緒獲得同步狀態。獨佔式超時獲取同步狀態,如果獲取不成功,在超時後會放棄而不會一直阻塞(這也是Lock相對於synchronized增加的功能)。

 

參考資料:

《Java併發程式設計的藝術》