JAVA併發包(一):AbstractQueuedSynchronizer

一、簡介

AbstractQueuedSynchronizer簡稱AQS,從名字理解,它是一個抽象隊列同步鎖,是重入鎖、讀寫鎖的基類。如果我們要全面學習java的併發鎖,首先要掌握AQS的加鎖機制。下面文章我將介紹AQS的結構,加鎖和解鎖方法。

二、什麼是AQS

1.AQS初認識

AQS的加鎖原理可以用下面的圖表示,每次調用加鎖時,會對int類型的成員變量state進行判斷。

加鎖:當state不等於0時,會把當前節點加到鏈表中,阻塞等待,直到被前驅喚醒,head指向自己時纔會去爭取鎖。爭取鎖的時候是用了unsafe.compareAndSwapInt方法,其實AQS裏面使用了大量Unsafe裏的方法,它是不安全的,我們一般很少直接調用,但是人家Doug Lea大神卻使用得遊刃有餘。

當state等於0,並且調用compareAndSwapInt方法成功時,表示獲得了鎖。

解鎖:獲得鎖的線程在調用解鎖方法後,會把head指向到下一個節點,並且喚醒下一個節點去競爭獲取鎖。

在這裏插入圖片描述

2.AQS類關係圖

從關係圖中可以看到AQS繼承了AbstractOwnableSynchronizer,與AbstractQueuedLongSynchronizer並列關係。

AbstractOwnableSynchronizer主要有兩個方法,用戶保存和獲取佔用鎖的線程。

AbstractQueuedLongSynchronizer裏面的方法實現基本與AbstractQueuedSynchronzier相同,名字的差別在於Long,其實也就是他的state成員變量是Long類型。它作用表現在可以保存更多的狀態位,在鎖重入、讀寫鎖的數量方面有更多的選擇。估計很少場景需要用到它。
在這裏插入圖片描述

3.基本數據結構

public abstract class AbstractQueuedSynchronizer
    extends AbstractOwnableSynchronizer
    implements java.io.Serializable {

	static final class Node {
        /** 指明一個節點是共享節點,可以理解是讀鎖 */
        static final Node SHARED = new Node();
        /** 排他節點,可以理解爲寫鎖 */
        static final Node EXCLUSIVE = null;

        /** 代表節點的線程已經被取消 */
        static final int CANCELLED =  1;
        /** 信號量,代表後繼節點需要阻塞等待 */
        static final int SIGNAL    = -1;
        /** 代表線程在另外一個隊列或者鏈表中等待,可以把condition理解成第二等待隊列,只有從第二隊列移到第一隊列才能參與鎖的競爭 */
        static final int CONDITION = -2;
        /** 傳播狀態,指示後繼節點可以無條參與讀鎖的競爭 */
        static final int PROPAGATE = -3;

        /** 等待狀態,爲以上幾種類型 */
        volatile int waitStatus;

        /** 上一個節點 */
        volatile Node prev;

        /** 下一個節點 */
        volatile Node next;

        /** 當前線程 */
        volatile Thread thread;

        /** 在conditin等待隊列中,指向下一個等待節點  */
        Node nextWaiter;
    }

	// 可以理解爲它就是第二等待隊列
	public class ConditionObject implements Condition, java.io.Serializable {
        /** 指向第一個等待節點 */
        private transient Node firstWaiter;
        /** 指向最後一個等待節點. */
        private transient Node lastWaiter;
	}
}

三、AQS的使用

1.加鎖

	// 一般加鎖會首先調用這個方法,參數爲加鎖的計數器,累加到成員變量state
	public final void acquire(int arg) {
		// 如果不能立刻,則加入到隊列中去獲取
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
	 }

	// 子類實現,一般是state成員變量加上arg
	protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
	 }

	// 這裏把一個節點加到隊列末端
	private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        enq(node);
        return node;
}

	// 入隊列
	private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

	// 等待隊列的節點獲取到鎖會返回true,否則阻塞等待
	final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                // 如果上一個節點是頭節點,說明可以參與鎖競爭了。並且鎖競爭成功則當前節點佔用鎖,把head指向自己
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
				
				// 競爭鎖失敗,判斷是否阻塞線程,下面分析這個方法
                if (shouldParkAfterFailedAcquire(p, node) &&
                	// 這裏是阻塞線程,用的是LockSupport.park(this)
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
            	// 失敗則說明是有異常了,則取消該線程的等待。後面分析這方面
                cancelAcquire(node);
        }
    }

	// 競爭鎖失敗時,判斷線程是否需要阻塞
	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            // 前驅狀態時signal,則需要阻塞等待喚醒
            return true;
        if (ws > 0) {
            // 前驅狀態大於0,說明是取消的狀態,則向前遍歷找到不大於0的前驅
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            /*
             * waitStatus must be 0 or PROPAGATE.  Indicate that we
             * need a signal, but don't park yet.  Caller will need to
             * retry to make sure it cannot acquire before parking.
             */
			// 到這一步說明前驅的狀態要麼是0,要麼是PROPAGATE(傳播狀態),則當前線程可以再次參與鎖競爭一次,如果第二次還沒有獲取到,則前驅狀態變爲sigal了會到第一步阻塞線程
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

	// 取消鎖競爭
	private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;

        node.thread = null;

        // 如果前驅爲取消狀態,則快速斷開所有這些前驅節點
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        // 前驅的下一個節點,
        Node predNext = pred.next;

        // 設置當前節點狀態爲取消
        node.waitStatus = Node.CANCELLED;

        // 如果當前節點是尾節點,則設置前驅爲尾節點
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            // 如果前驅不爲頭節點,並且(前驅狀態是sigal,或者狀態小於0並且設置狀態sigal成功),並且前驅線程不爲null,則設置前驅的下一個節點爲當前節點的下一個節點
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
            	// 到這步說明需要參與鎖競爭了
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }

2.解鎖

	// 解鎖相對簡單
	public final boolean release(int arg) {
		// 這裏一般是狀態state減去arg
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
            	// 喚醒後繼節點
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

四、總結

AQS是重入鎖和同步隊列的基礎類,是學習JUC的第一課。以上是我對AQS的瞭解,如有不對之處歡迎指正。

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