AbstractQueuedSynchronizer源碼分析(二):獨佔鎖的獲取與釋放

1、典型實現:ReentrantLock

ReentrantLock就是一個典型的不響應中斷的獨佔鎖,那就從ReentrantLock的lcok()開始走讀不響應中斷的獨佔鎖的實現邏輯。
這裏我們基於ReentrantLock默認的非公平鎖的lock(),公平鎖我們留待分析ReentrantLock的源碼中進行詳細分析。
先上NonfairSync代碼

static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * 立即嘗試獲取鎖,失敗後再去走正常的鎖獲取流程。
         * (非公平不需要根據隊列順序來獲取鎖,直接嘗試獲取鎖可以很高的提升鎖的效率)
         */
        final void lock() {
        	//0表示還未有線程獲得鎖
            if (compareAndSetState(0, 1))
            	//設置獨佔鎖的擁有者
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

NonfairSync的類關係是NonfairSync extends Sync,Sync extends AbstractQueuedSynchronizer。這裏採用的模板模式進行設計的,AbstractQueuedSynchronizer的acquire(int arg)方法已經完成了不響應中斷的獨佔鎖獲取邏輯,而tryAcquire(int acquires)是對state鎖標識位的管理,需要子類實現。

2、AbstactQueuedSynchronizer不響應中斷的獨佔鎖方法介紹

AbstactQueuedSynchronizer中供子類實現的方法

方法名 作用
boolean tryAcquire(int arg) 嘗試以獨佔模式獲取。此方法應該查詢對象的state是否允許以獨佔模式獲取它,如果允許,那麼可以獲取它。
boolean tryRelease(int arg) 嘗試以獨佔模式設置state來反映對鎖的釋放。
boolean isHeldExclusively() 同步器是否在獨佔模式下被當前線程佔用。

AbstactQueuedSynchronizer中實現的方法

方法名 作用
void acquire(int arg) 在獨佔模式下獲取鎖,不響應中斷
Node addWaiter(Node mode) 以mode模式爲當前線程創建node節點,並且加入CLH隊列中。
boolean compareAndSetTail(Node expect, Node update) CAS更新tail屬性
Node enq(final Node node) 將node節點加入隊尾,若隊列未初始化,先進行初始化,在同步器整個生命週期中只會初始化一次
boolean compareAndSetHead(Node update) CAS更新head屬性
boolean acquireQueued(final Node node, int arg) 以獨佔不響應中斷模式爲已在隊列中的線程獲取鎖
void setHead(Node node) 把node設爲CLH隊列的head節點,並將node節點thread釋放、把原head節點從CLH隊列中釋放
boolean shouldParkAfterFailedAcquire(Node pred, Node node) 尋找安全點,當找到安全點後進行阻塞
void LockSupport.park(Object blocker); 當前線程阻塞
boolean compareAndSetWaitStatus(Node node,int expect,int update) CAS更新waitStatus屬性
boolean parkAndCheckInterrupt() 阻塞當前線程,並在喚醒後返回中斷狀態
void cancelAcquire(Node node) 取消線程獲取鎖
boolean compareAndSetNext(Node node,Node expect,Node update) CAS更新next屬性
void unparkSuccessor(Node node) 喚醒後繼節點中的線程
boolean compareAndSetState(int expect, int update) CAS更新state屬性
void selfInterrupt() 標記當前線程中斷狀態
  1. CAS方法介紹
    在上面的方法中,我們發現#compareAndSetTail,#compareAndSetHead,#compareAndSetWaitStatus,#compareAndSetNext,#compareAndSetState方法都是CAS方法。拉出源碼一看,方法體內部是通過調用unsafe#compareAndSwapObject方法屬性的更新。
    關於CAS的介紹,可以參考【Java中的CAS應用
    下面源碼中,展示了AQS那些屬性可以進行CAS操作。
	private static final Unsafe unsafe = Unsafe.getUnsafe();
	// AbstractQueuedSynchronizer.class的屬性
	// state屬性下標,以下雷同
    private static final long stateOffset;
    private static final long headOffset;
    private static final long tailOffset;
    // Node.class的屬性
    // waitStatus屬性下標,以下雷同
    private static final long waitStatusOffset;
    private static final long nextOffset;

    static {
        try {
        	// 獲取屬性state在AbstractQueuedSynchronizer類中的下標,以下雷同
            stateOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("state"));
            headOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("head"));
            tailOffset = unsafe.objectFieldOffset
                (AbstractQueuedSynchronizer.class.getDeclaredField("tail"));
            waitStatusOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("waitStatus"));
            nextOffset = unsafe.objectFieldOffset
                (Node.class.getDeclaredField("next"));

        } catch (Exception ex) { throw new Error(ex); }
    }
    private final boolean compareAndSetHead(Node update) {
        return unsafe.compareAndSwapObject(this, headOffset, null, update);
    }
    private final boolean compareAndSetTail(Node expect, Node update) {
        return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
    }
    private static final boolean compareAndSetWaitStatus(Node node, int expect, int update) {
        return unsafe.compareAndSwapInt(node, waitStatusOffset, expect, update);
    }
    private static final boolean compareAndSetNext(Node node, Node expect, Node update) {
        return unsafe.compareAndSwapObject(node, nextOffset, expect, update);
    }
  1. acquire(int arg)方法源碼及註釋
	// 在獨佔模式下獲取鎖,忽略中斷;參數arg傳輸給#tryAcquire方法,可以表示任何你想要表達的意思。
	public final void acquire(int arg) {
        // 1、tryAcquire(arg),調用同步器實現的tryAcquire獲取鎖,返回true流程結束,否則進入2
		// 2、addWaiter(Node.EXCLUSIVE),將當前線程封裝成node節點,加入到CLH隊列尾部,進入3
		// 3、若線程從queued中被喚醒時,返回中斷狀態,若爲true,進入4
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
			// 4、將中斷狀態傳輸給當前線程
            selfInterrupt();
    }
    /** 
	* 這裏的tryAcquire需要AQS的子類(同步器)進行實現,提供給父類的模板方法調用。
	* return 返回true表示獲取鎖成功,否則表示獲取鎖失敗
	*/
    protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }
  1. addWaiter(Node mode) 方法源碼及註釋
	/**
     * 以mode模式爲當前線程創建node節點,並且加入CLH隊列中。
     *
     * @param mode Node.EXCLUSIVE 獨佔模式, Node.SHARED 共享模式
     * @return 創建的node節點
     */
    private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // node節點嘗試快速入隊,若失敗進入enq()方法
        Node pred = tail;
        if (pred != null) {
			// 設置node節點的前驅,無多線程併發,放在CAS外
            node.prev = pred;
            if (compareAndSetTail(pred, node)) {
				// 存在多個線程併發操作pred節點,必須在節點入隊成功後,把node設爲pred節點的後繼,。
				// 必須要CAS成功後才能操作。
                pred.next = node;
                return node;
            }
        }
		// node節點入隊
        enq(node);
        return node;
    }
  1. enq(final Node node)方法源碼及註釋
	/**
     * 將node節點加入隊尾,若隊列未初始化,先進行初始化,在同步器的整個生命週期中只會初始化一次
     * @param node 加入隊尾的node節點
     * @return node節點的前驅節點
     */
    private Node enq(final Node node) {
		// 通過循環,不斷嘗試將node加入隊尾;直至成功,跳出循環。
        for (;;) {
            Node t = tail;
            if (t == null) { 
				// 初始化隊列,Node節點中不包含線程
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
					// 加入隊尾成功,跳出循環。
                    t.next = node;
                    return t;
                }
            }
        }
    }
  1. acquireQueued(final Node node, int arg)源碼及註釋
	/**
     * 以獨佔不響應中斷模式獲取已在隊列中的線程,直至tryAcquire(arg)方法返回true(獲取鎖成功),並返回中斷狀態。
     *
     * @param node 當前線程的node節點
     * @param arg acquire方法參數
     * @return 如果等待時被中斷,返回true,否則返回false
     */
    final boolean acquireQueued(final Node node, int arg) {
		// 失敗狀態
        boolean failed = true;
        try {
			// 中斷狀態
            boolean interrupted = false;
			// 進行循環,直至tryAcquire成功後,返回中斷狀態。
            for (;;) {
				// 獲取前驅節點
                final Node p = node.predecessor();
				// 1、前驅節點爲head && tryAcquire獲取鎖返回true,返回中斷狀態,否則進入2
                if (p == head && tryAcquire(arg)) {
					// 將該節點設爲head節點,並將node節點中的線程釋放
					setHead(node); 
					// 釋放前驅節點的引用,幫助GC
                    p.next = null; 
					// 標記失敗狀態
                    failed = false; 
                    return interrupted;
                }
				// 2、shouldParkAfterFailedAcquire,判斷node節點是否應該阻塞,返回true進入3,否則進入1
				// 3、parkAndCheckInterrupt,node節點進入阻塞狀態,被喚醒後返回中斷狀態,返回true,標記interrupted爲true,否則不標記;隨後進入1
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
			// 判斷失敗狀態,若爲true;取消node節點acquire。根據上面流程看,個人認爲只有在出現異常時纔會出現cancelAcquire(node)的情況。
            if (failed)
                cancelAcquire(node);
        }
    }
  1. setHead(Node node)源碼及註釋
	/**
     *把node設爲CLH隊列的head節點,並從CLH隊列中出列。
	 *
     */
    private void setHead(Node node) {
		// 設爲head節點
        head = node;
		// 釋放node節點中線程
        node.thread = null;
		// 釋放對前驅節點的引用,幫助GC
        node.prev = null;
    }
  1. shouldParkAfterFailedAcquire(Node pred, Node node)源碼及註釋
	/**
     * 獲取鎖失敗後,根據前驅判斷當前節點是否應該進行阻塞。
     *
     * @param pred 前驅節點
     * @param node 當前節點
     * @return 返回true,當前線程應該阻塞
     */
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
		// 獲取pred節點waitStatus狀態值
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
			 /*
             * 若pred節點爲SIGNAL狀態,表示pred節點釋放鎖時會喚醒(unpark)後繼節點;同時也表示node節點可以安全的進行阻塞(park)了。
             */
            return true;
        if (ws > 0) {
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
			 /*
             * waitStatus>0表示前驅節點被取消,放棄當前的pred節點,不斷循環尋找前驅節點,直至尋找到一個未被取消的節點。
             */
            do {
				// 1、pred = pred.prev,獲取pred的前驅節點
				// 2、node.prev = pred.prev,當前節點指向新的前驅節點
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
			// 尋找成功後,前驅節點next引用node節點
            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.
             */
			 /*
             * 上面兩種狀態都不滿足時,通過CAS操作更新pred節點爲waitStatus=SIGNAL
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
		// 不能park,需要不斷遍歷,直到前驅節點waitStatus=SIGNAL
        return false;
    }
  1. LockSupport.park(Object blocker)源碼
    具體關於LockSupport的內容,可以參考【LockSupport的使用】,不做贅述。
	//這行代碼的作用就是使線程阻塞在這裏,等待其他的線程調用該unpark該線程,喚醒線程後繼續執行後面方法
	LockSupport.park(this);
  1. parkAndCheckInterrupt()源碼及註釋
	/**
	* 現在阻塞在當前,當前驅線程調用unpark方法後,可以喚醒阻塞,並返回中斷狀態。
	*/
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
  1. cancelAcquire(Node node)源碼及註釋
	/**
     * Cancels an ongoing attempt to acquire.
     *
     * @param node the node
     */
	 /**
     * 取消node節點,
	 * 若node節點的prev是正常節點,連接node.prev和node.next
	 * 若node節點的prev是非正常狀態的節點,就喚醒node節點的後繼節點
     */
    private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
		// node不存在,忽略後面邏輯
        if (node == null)
            return;
		// 釋放node節點中線程
        node.thread = null;

        // Skip cancelled predecessors
		// 跳過所有被取消的前驅
        Node pred = node.prev;
        while (pred.waitStatus > 0)
			// 1.pred = pred.prev 將pred的前驅節點設爲pred
			// 2.node.prev = pred 將pred設置爲node的前置節點
			// 這裏的邏輯等於是放棄原來的pred節點,將pred.prev設爲新的pred
            node.prev = pred = pred.prev;

        // predNext is the apparent node to unsplice. CASes below will
        // fail if not, in which case, we lost race vs another cancel
        // or signal, so no further action is necessary.
        // 取出pred前驅原值
		Node predNext = pred.next;
		
        // Can use unconditional write instead of CAS here.
        // After this atomic step, other Nodes can skip past us.
        // Before, we are free of interference from other threads.
		// 設置node節點狀態爲CANCELLED
        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
		// 如果node爲tail節點,就將pred設爲tail節點
        if (node == tail && compareAndSetTail(node, pred)) {
			// 釋放pred節點的next屬性
            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.
			// 若node後繼節點需要喚醒,嘗試將pred的next屬性設爲後繼節點。否則就喚醒node節點
            int ws;
			// 1.pred節點不爲head節點,ture進入2,否則進入6
			// 2.pred節點waitStatus爲SIGNAL,true進入4,否則進入3
			// 3.當pred未被取消,並將其waitStatus更新爲SIGNAL成功後進入4,否則進入6
			// 4.若pred的線程不爲空進入5,否則進入6
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                // 5.若node.next節點是正常節點,將node.next節點地址指向pred.next地址,等於放棄了node節點了。
				Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
				// 6若pred節點不滿足上面的情況,就需要直接喚醒node的後繼節點,即node.next
                unparkSuccessor(node);
            }
			// 幫助GC
            node.next = node; // help GC
        }
    }
  1. unparkSuccessor(Node node)源碼及註釋
	/**
	 * 喚醒node的後繼節點
	 */
    private void unparkSuccessor(Node node) {
        /*
         * If status is negative (i.e., possibly needing signal) try
         * to clear in anticipation of signalling.  It is OK if this
         * fails or if status is changed by waiting thread.
         */
		// 狀態爲負,嘗試清除狀態 
        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        /*
         * Thread to unpark is held in successor, which is normally
         * just the next node.  But if cancelled or apparently null,
         * traverse backwards from tail to find the actual
         * non-cancelled successor.
         */
        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
			// 若node.next已經被取消,那需要重tail變量找到離node最近的那個節點,作爲node節點後繼節點喚醒
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
		// 喚醒node的後繼節點
        if (s != null)
            LockSupport.unpark(s.thread);
    }
  1. selfInterrupt()源碼及註釋
	/**
     * Convenience method to interrupt current thread.
     */
	/**
     * 將中斷狀態傳輸給當前線程
     */
    static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

上面代碼走讀添加了很多註釋,能夠清晰的理解到不響應中斷鎖的獲取方式。以上的註釋和理解的
篇幅限制,相關的知識點就貼上相關鏈接了,也是我在走讀代碼時遇到的不懂,然後去了解的。若讀者朋友們對其不是很熟悉,也可以先閱讀相關文章,主要是【CAS】和【LockSupport】。

不響應中斷的獲取獨佔鎖的流程圖

附上不響應中斷的獲取獨佔鎖的流程圖
不響應中斷的獲取獨佔鎖

3、AbstactQueuedSynchronizer響應中斷的獨佔鎖方法

在介紹響應中斷的獨佔鎖之前,需要了解什麼是【中斷】,在瞭解中斷後。我們繼續看acquireInterruptibly(int arg) throws InterruptedException方法,對比acquire(int arg)發現,acquireInterruptibly方法在獲取到線程中斷標記後,立即拋出InterruptedException異常以做響應。

方法名 作用
void acquireInterruptibly(int arg) throws InterruptedException 嘗試回去哦響應中斷的獨佔鎖
doAcquireInterruptibly(int arg) throws InterruptedException 將線程加入CLH隊列中進行管理

與不響應中斷鎖不同主要是以上兩個方法,其他相同方法不在此贅述。

  1. void acquireInterruptibly(int arg) throws InterruptedException 若線程被標記中斷,直接拋出中斷異常
	public final void acquireInterruptibly(int arg)
            throws InterruptedException {
		// 線程中斷立即響應
        if (Thread.interrupted())
            throw new InterruptedException();
		// 線程獲取獨佔鎖失敗後,進行入隊操作
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
  1. void doAcquireInterruptibly(int arg) throws InterruptedException 若線程被標記中斷,直接拋出中斷異常
	private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
		// 入隊
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
			// 直至獲取到鎖或線程被標記中斷
            for (;;) {
				// head節點(dummy)的next可以嘗試獲取鎖
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    // node成爲新的head節點
					setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
				// 1、尋找安全點,成功後進入2,否則再次進入循環
				// 2、進入阻塞,阻塞被喚醒後返回中斷狀態,若被標記中斷,進入3,否則再次進入循環
				// 3、拋出中斷異常
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
			// 拋出中斷異常後,取消node節點
            if (failed)
                cancelAcquire(node);
        }
    }

4、AbstactQueuedSynchronizer獨佔鎖的釋放

方法名 作用
boolean release(int arg) 釋放獨佔模式下的鎖
void unparkSuccessor(Node node) 喚醒後繼節點中的線程
  1. release(int arg)源碼及註釋
public final boolean release(int arg) {
		// 調用子類實現tryRelease方法,返回ture表示release成功,否則結束流程
        if (tryRelease(arg)) {
			// 釋放鎖成功,喚醒隊列中第一個node節點中的線程,因爲head節點是dummy節點,所以喚醒head.next節點中線程。
            Node h = head;
			// 此時的head節點的waitStatus應該爲-1,是在調用acquire時設置的。
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

5、總結

從源碼的走讀和源碼註解理解,瞭解到AbstactQueuedSynchronizer獨佔鎖如何管理線程,如何阻塞,阻塞在何處,何時被喚醒,響應中斷和不響應中斷如何管理中斷狀態。定義了從嘗試獲取–阻塞–喚醒–嘗試獲取的整個流程。另外也知道鎖的狀態(state)管理完全是依賴子類對tryAcquire(int arg)和tryRelease(int arg)的實現。換句話說鎖狀態(state)是交由子類實現管理,AQS並不關心鎖的管理,它只關心Thead何時如何怎樣去獲取鎖。
下一章節介紹共享鎖,請各位讀者持續關注。

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