隊列同步器詳解

隊列同步器介紹 

隊列同步器AbstractQueuedSynchronizer,是用來構建鎖或者其餘同步組件的基礎框架,它使用了一個int成員變量表示同步狀態,經過內置的FIFO隊列來完成資源獲取線程的排隊工做。node

  同步器的主要使用方式是繼承,通常做爲同步器組件的靜態內部類,在同步器中僅定義了與狀態相關的方法,且狀態既能夠獨佔獲取也能夠共享獲取,這樣就能夠實現不一樣的同步組件(ReetrantLock、CountDownLatch等)。同步組件利用同步器進行鎖的實現,它簡化了鎖的實現方式,屏蔽了同步狀態管理、線程的排隊、等待與喚醒等頂層操做。  同步器的設計是基於模板方法模式的,使用者須要繼承同步器並重寫指定的方法,隨後將同步器組合在自定義同步組件的實現中,並調用同步器提供的模板方法,而這些模板方法將會調用使用者重寫的方法。
安全

同步器提供了3個方法來修改和訪問同步狀態:
數據結構

一、getState():獲取當前同步狀態併發

二、setState(int newState):設置當前同步狀態框架

三、compareAndSetState(int expect, int update):使用CAS設置當前狀態,該方法能保證操做的原子性。ui

隊列同步器的實現

下面主要從實現角度分析同步器是如何完成線程同步的,主要包括:同步隊列、獨佔式同步狀態的獲取和釋放、共享式同步狀態的獲取和釋放等核心數據結構與模板方法。spa

一、同步隊列

同步器利用同步隊列來完成同步狀態的管理。它是一個FIFO的雙向隊列,當線程獲取狀態失敗時,同步器會將當前線程和等待狀態等信息包裝成尾節點放入同步隊列中,同時會阻塞當前線程。當同步狀態釋放時,會喚醒首節點,使其嘗試獲取同步狀態。線程

在節點加入過程當中,會涉及到併發的問題,因此這個加入過程要確保線程安全,所以同步器提供了一個基於CAS設置尾節點的方法:compareAndSetTail(Node expect, Node update)。設計

在設置首節點過程當中,首節點是經過獲取同步狀態成功的線程設置的,因爲只有一個線程可以獲取到同步狀態,因此設置首節點的方法並不須要使用CAS來保證。只須要將首節點設置原首節點的後續節點並斷開原節點的next引用便可。code

二、獨佔式同步狀態的獲取

同步器是經過acquire()方法來獲取同步狀態的,該方法是模板方法:

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

一、調用自定義同步器實現的tryAcquire(arg)獲取同步狀態,該方法保證線程安全。

二、若是獲取同步狀態失敗,則構造同步節點(獨佔式),經過addWaiter方法放入同步隊列的尾部

final boolean acquireQueued(final Node node, int arg) {
        try {
            boolean interrupted = false;
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    return interrupted;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }

三、節點在加入同步隊列後,在同步隊列中全部的節點都處於自旋狀態,可是隻有前驅節點是頭節點才能嘗試獲取同步狀態。一是由於頭節點是已經獲取到同步狀態的節點,當頭節點的線程釋放同步狀態以後,將會喚醒後繼節點,後繼節點被換喚醒後須要檢查本身的前驅節點是不是頭節點。而是由於這樣處理能夠維護同步隊列的FIFO原則。

三、獨佔式同步狀態的釋放

public final boolean release(int arg) {
        if (tryRelease(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

該方法執行時,會喚醒節點的後繼節點線程,經過調用unparkSuccessor(h)方法來喚醒處於等待狀態的線程。

四、共享式同步狀態獲取

與獨佔式的區別在於:在同一時刻可否有多個線程同時獲取到同步狀態。例如文件讀寫,在同一時刻,若是進行讀操做,那麼寫操做會被阻塞,可是能夠同時有多個讀操做,這種讀操做就是共享式的。相反,寫操做只能有一個,而且阻塞其餘全部的寫操做和讀操做。

public final void acquireShared(int arg) {
        if (tryAcquireShared(arg) < 0)
            doAcquireShared(arg);
    }
 private void doAcquireShared(int arg) {
        final Node node = addWaiter(Node.SHARED);
        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();
                        return;
                    }
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } catch (RuntimeException ex) {
            cancelAcquire(node);
            throw ex;
        }
    }

一、調用tryAcquireShared(arg)嘗試獲取同步狀態,若是方法值大於等於0,則表示獲取同步狀態成功。

二、在方法doAcquireShared自旋過程當中,若是前驅節點爲頭節點時,嘗試獲取同步狀態。自旋結束的條件就是tryAcquireShared方法的返回值大於等於0.

五、共享式同步狀態釋放

public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            Node h = head;
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }

這個與獨佔式相似,都是釋放後將會喚醒處於等待狀態的節點。惟一的區別是這個方法必須支持併發,由於釋放同步狀態的操做會來自多個線程。因此tryReleaseShared(arg)是經過CAS保證的。

相關文章
相關標籤/搜索