併發編程 - AbstractQueuedSynchronizer

獨佔式

獨佔式獲取

如果tryAcquire(arg)獲取鎖成功,直接返回;
如果獲取失敗,調用acquireQueued(…),根據其結果,判斷是否調用線程中斷。

	public final void acquire(int arg) {
		/* 如果獲取獨佔鎖失敗,並且在等待隊列中獲取鎖成功  */
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
			/* 線程中斷 */
            selfInterrupt();
    }

	static void selfInterrupt() {
        Thread.currentThread().interrupt();
    }

tryAcquire(…)是需要自己實現的抽象方法。

	protected boolean tryAcquire(int arg) {
        throw new UnsupportedOperationException();
    }

接下來看下,addWaiter(…)方法。

將當前線程封裝成節點,在等待隊列的尾部插入。

	private Node addWaiter(Node mode) {
		/* 將當前線程包裝成一個節點,模式是獨佔式 */
        Node node = new Node(Thread.currentThread(), mode);
        // 獲取尾節點
        Node pred = tail;
        if (pred != null) {
            node.prev = pred;
            /* cas方式設置尾節點,因爲有可能有多個節點此刻想在隊列尾部插入,
            使用cas保證同一時刻只有一個節點會插入成功 */
            if (compareAndSetTail(pred, node)) {
                pred.next = node;
                return node;
            }
        }
        /* 之前有獲取尾節點的操作,但是,有可能尾節點不存在,這時候執行enq(...) */
        enq(node);
        return node;
    }

然後看下上面說的enq(…)方法:

上文說到,enq(…)是尾節點不存在時調用的方法。
使用enq(…)構造一個新的等待隊列。

	private Node enq(final Node node) {
		/* 使用循環,保證構造新的等待隊列以及插入尾節點一定成功 */
        for (;;) {
            Node t = tail;
            /* 此時,等待隊列仍舊未存在 */
            if (t == null) { 
            	/* 構造新的等待隊列,設置頭節點以及尾節點 */
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
            	/* 此時,等待隊列已經存在,執行在尾部插入新節點 */
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }

最後剩下acquireQueued(…)。
這個方法做的是在等待隊列中的節點,如何嘗試獲取獨佔鎖。

	final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
            	/* 獲取前驅節點 */
                final Node p = node.predecessor();
                /* 前驅節點是頭節點,並且獲取鎖成功 */
                if (p == head && tryAcquire(arg)) {
                	/* 設置該節點爲頭節點,因爲只有頭節點有機會獲取鎖 */
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                /* 獲取鎖失敗,判斷是否要阻塞 && 阻塞 */
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

	private void setHead(Node node) {
        head = node;
        node.thread = null;
        node.prev = null;
    }

現在看下shouldParkAfterFailedAcquire(…)方法:

判斷在獲取鎖失敗後,是否應當阻塞。

主要以是否成功設置節點狀態爲SIGNAL來判斷

	private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        /* 節點狀態爲取消 */
        if (ws > 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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

線程阻塞:

	private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }

最後,是cancalAcquire(…)的分析:

	private void cancelAcquire(Node node) {
        /* 如果節點不存在,直接返回 */
        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;

        // 圖1
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
           
            int ws;
            // 圖2
            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);
            // 圖3
            } else {
                unparkSuccessor(node);
            }

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

圖片1:
在這裏插入圖片描述

圖2:
在這裏插入圖片描述

圖3:
在這裏插入圖片描述

獨佔式釋放

喚醒並釋放後繼節點

	public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            /* 頭節點存在,並且還沒有釋放鎖 */
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

tryRelease(…)方法同樣是抽象方法:

	protected boolean tryRelease(int arg) {
        throw new UnsupportedOperationException();
    }

然後看下unparkSuccessor(…)方法:

	private void unparkSuccessor(Node node) {
       
        int ws = node.waitStatus;
        if (ws < 0)
        	/* 設置爲初始狀態 */
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        /* 後繼節點爲空或者後繼節點是取消狀態 */
        if (s == null || s.waitStatus > 0) {
            s = null;
            /* 重新選取可用的後繼節點 */
            /* 從尾節點開始,選取其前驅節點 */
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        /* 可用的後繼節點 */
        if (s != null)
        	/* 喚醒線程 */
            LockSupport.unpark(s.thread);
    }
獨佔式獲取 + 響應中斷

對於中斷,拋出異常,而已。
之前的只是設置狀態而已,沒有對中斷作出實質性處理。

	public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }
	private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    /* 拋出異常 */
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
獨佔式獲取 + 響應中斷 + 等待超時

節點在等待隊列中等待過程中,增加超時

	public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        return tryAcquire(arg) ||
            doAcquireNanos(arg, nanosTimeout);
    }
	private boolean doAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException {
        if (nanosTimeout <= 0L)
            return false;
        /* 從節點加入到等待隊列中,開始計算截止時間 */
        final long deadline = System.nanoTime() + nanosTimeout;
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return true;
                }
                /* 如果前驅節點是頭節點,嘗試獲取鎖。。。然後計算超時時間剩餘 */
                /* 如果不是頭節點,直接計算剩餘時間 */
                /* 無論是上述哪種,節點都已經加入到等待隊列中了 */
                nanosTimeout = deadline - System.nanoTime();
                if (nanosTimeout <= 0L)
                    return false;
                if (shouldParkAfterFailedAcquire(p, node) &&
                    nanosTimeout > spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
                if (Thread.interrupted())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

共享式

同一時刻,可以有多個線程獲取到鎖

共享式獲取
	public final void acquireShared(int arg) {
		/* 如果獲取鎖失敗 */
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
	private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head) {
                    int r = tryAcquireShared(arg);
                    if (r >= 0) {
                        setHeadAndPropagate(node, r);
                        p.next = null; // help GC
                        if (interrupted)
                            selfInterrupt();
                        failed = false;
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

跟前面不太一樣的地方呢,是setHeadAndPropagate(…)

除了設置頭節點外,主要做的是對後繼的共享模式的節點喚醒。

	private void setHeadAndPropagate(Node node, int propagate) {
		/* 獲取舊的頭節點 */
        Node h = head; 
        /* 設置新的頭節點 */
        setHead(node);
        
        /* 後繼節點嘗試獲取鎖的條件 - 判斷是否有資源 */
        /* propagate > 0 表示有資源 */
        /* h == null 表示又有設置新的頭節點,舊的頭節點自然是空的,因爲是現在有資源可用,纔可以設置新的頭節點來獲得  */
        /* h.waitStatus < 0 表示這個頭節點還沒有釋放資源 */
        /* (h = head) == null 表示之前的舊的頭節點爲空,node設置爲頭節點,同樣是現在有資源可用,纔可以設置 */
        /* h.waitStatus < 0 表示這個頭節點還沒有釋放資源 */
        if (propagate > 0 || h == null || h.waitStatus < 0 ||
            (h = head) == null || h.waitStatus < 0) {
            Node s = node.next;
            if (s == null || s.isShared())
                /* 喚醒後繼節點,設置狀態爲propagate */
                doReleaseShared();
        }
    }

爲什麼要對新、舊頭節點有這個判斷?我個人認爲,一個是爲了提高效率(舊的頭節點對後繼節點的喚醒,不會因爲有新的頭節點了,而停止,歷任頭節點都會去做這個對後繼節點的喚醒,可以提高效率),另一個是爲了避免極端情況(舊的頭節點還沒有釋放同步狀態,已經開始設置新的頭節點,此時同步狀態還在舊的頭節點這裏)。

共享式釋放
	private void doReleaseShared() {
        
        /* 循環保證當前的操作成功,也就是喚醒後繼節點,設置狀態爲propagate */
        for (;;) {
            Node h = head;
            /* 等待隊列中至少存在一個可用節點 */
            if (h != null && h != tail) {
                int ws = h.waitStatus;
                if (ws == Node.SIGNAL) {
                	/* 當前如果修改失敗,不等待,直接交給下一次循環處理 */
                    if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
                        continue;            // loop to recheck cases
                    /* 上面的狀態修改成功,喚醒後繼節點 */
                    unparkSuccessor(h);
                }
                else if (ws == 0 &&
                         !compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
                    continue;                // loop on failed CAS
            }
            /* 因爲是共享式,有可能現在有多個線程在執行這個方法,頭節點有可能已經改變了 */
            if (h == head)                
                break;
        }
    }

與獨佔式的方式的區別呢,顯而易見,增加了對節點的狀態修改爲propagate。此外呢,增加了for(; ; ) 循環以及if (h == head)
break;判斷,是有考慮現在有多個線程在執行,保證當前的線程操作成功,喚醒頭節點的後繼節點

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