【Java併發】-- AQS 原理


AQS是什麼?

在 Lock 中,用到了一個同步隊列 AQS,全稱 AbstractQueuedSynchronizer,它是一個同步工具, 也是 Lock 用來實現線程同步的核心組件, 如果你搞懂了 AQS,那麼 J.U.C 中絕大部分的工具都能輕鬆掌握;

此篇博客所有源碼均來自JDK 1.8


AQS的內部實現

AQS 隊列內部維護的是一個 FIFO 的雙向鏈表,這種結構的特點是每個數據結構都有兩個指針,分別指向直接的後繼節點和直接前驅節點。所以雙向鏈表可以從任意一個節點開始很方便的訪問前驅和後繼。每個 Node 其實是由一個線程封裝,當線程爭搶鎖失敗後會封裝成 Node 加入到 ASQ 隊列中去;當獲取鎖的線程釋放鎖以後,會從隊列中喚醒一個阻塞的節點(阻塞的線程)。
AQS中連個重要的成員變量

private transient volatile Node head; 		// 頭節點
private transient volatile Node tail; 		// 尾節點

在這裏插入圖片描述


Node的內部實現

    static final class Node {
        /** Marker to indicate a node is waiting in shared mode */
        // 共享模式時
        static final Node SHARED = new Node();
        /** Marker to indicate a node is waiting in exclusive mode */
        // 獨佔模式時
        static final Node EXCLUSIVE = null;

        /** waitStatus value to indicate thread has cancelled */
        static final int CANCELLED =  1;  //節點從同步隊列中取消
        /** waitStatus value to indicate successor's thread needs unparking */
        static final int SIGNAL    = -1; //後繼節點的線程處於等待狀態,
      	  								如果當前節點釋放同步狀態會通知後繼節點,
        					使得後繼節點的線程能夠運行; (只有處於singal狀態的節點才能被喚醒)
        /** waitStatus value to indicate thread is waiting on condition */
        static final int CONDITION = -2;
        /**
         * waitStatus value to indicate the next acquireShared should
         * unconditionally propagate
         */
        static final int PROPAGATE = -3; // 表示下一次共享式同步狀態獲取將會無條件傳播下去

        volatile int waitStatus; //節點狀態       
        volatile Node prev;
        volatile Node next;
        volatile Thread thread; //加入同步隊列的線程引用

        Node nextWaiter;   // condition隊列中的後繼節點
			// 是否爲共享鎖 	
            final boolean isShared() {
            return nextWaiter == SHARED;
        }

        /**
         * 返回前驅節點
         */
        final Node predecessor() throws NullPointerException {
            Node p = prev;
            if (p == null)
                throw new NullPointerException();
            else
                return p;
        }

        Node() {    // Used to establish initial head or SHARED marker
        }	
        // 將線程構造成一個node節點,添加到等待隊列(condition隊列)
        Node(Thread thread, Node mode) {     // Used by addWaiter
            this.nextWaiter = mode;
            this.thread = thread;
        }

        // 等待隊列用到的方法
        Node(Thread thread, int waitStatus) { // Used by Condition
            this.waitStatus = waitStatus;
            this.thread = thread;
        }
    }

AQS的兩種功能?

獨佔鎖和共享鎖

獨佔鎖:

每次只能有一個線程持有鎖,比如ReentrantLock就是以獨佔方式實現的互斥鎖;

幾個重要的方法:

void acquire(int arg):獨佔式獲取同步狀態,如果獲取失敗則插入同步隊列進行等待;
void acquireInterruptibly(int arg):與acquire方法相同,但在同步隊列中進行等待的時候可以檢測中斷;
boolean tryAcquireNanos(int arg, long nanosTimeout):在acquireInterruptibly基礎上增加了超時等待功能,在超時時間內沒有獲得同步狀態返回false;
boolean release(int arg):釋放同步狀態,該方法會喚醒在同步隊列中的下一個節點

入口:

acquire(int arg) 方法爲AQS提供的模板方法,該方法爲獨佔式獲取同步狀態,但是該方法對中斷不敏感,也就是說由於線程獲取同步狀態失敗加入到CLH同步隊列中,後續對線程進行中斷操作時,線程不會從同步隊列中移除。代碼如下:

// 獨佔鎖入口    
public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
  • tryAcquire:去嘗試獲取鎖,獲取成功則設置鎖狀態並返回true,否則返回false。該方法自定義同步組件自己實現,該方法必須要保證線程安全的獲取同步狀態。
  • addWaiter:如果tryAcquire返回FALSE(獲取同步狀態失敗),則調用該方法將當前線程加入到CLH同步隊列尾部。
  • acquireQueued:當前線程會根據公平性原則來進行阻塞等待(自旋),直到獲取鎖爲止;並且返回當前線程在等待過程中有沒有中斷過。
  • selfInterrupt:產生一箇中斷。

共享鎖:

允許多個線程同時獲取鎖,併發訪問共享資源,例如:ReentrantReadWriteLock,CountDownLatch;

幾個重要的方法:

void acquireShared(int arg):共享式獲取同步狀態,與獨佔式的區別在於同一時刻有多個線程獲取同步狀態;
void acquireSharedInterruptibly(int arg):在acquireShared方法基礎上增加了能響應中斷的功能;
boolean tryAcquireSharedNanos(int arg, long nanosTimeout):在acquireSharedInterruptibly基礎上增加了超時等待的功能; boolean releaseShared(int arg):共享式釋放同步狀態

入口:

// 共享鎖入口
public final void acquireShared(int arg) {        
    if (tryAcquireShared(arg) < 0)            //獲取失敗,自旋獲取同步狀態            
        doAcquireShared(arg);    
}

tryAcquireShared(int arg)方法嘗試獲取同步狀態,返回值爲int,當其 >= 0 時,表示能夠獲取到同步狀態,這個時候就可以從自旋過程中退出。
如果獲取失敗則調用doAcquireShared(int arg)自旋方式獲取同步狀態,共享式獲取同步狀態的標誌是返回 >= 0 的值表示獲取成功;
acquireShared(int arg)方法不響應中斷,與獨佔式相似,AQS也提供了響應中斷、超時的方法,分別是:acquireSharedInterruptibly(int arg)、tryAcquireSharedNanos(int arg,long nanos),這裏就不做解釋了。

總結

AQS的主要使用方式是繼承,子類通過繼承同步器並實現它的抽象方法來管理同步狀態,後續會詳細分析子類,ReentrantLock以及ReentrantReadWriteLock的具體實現。

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