深入學習Lock鎖(1)——佇列同步器

江左煤郎發表於2018-09-13

1. 佇列同步器

    佇列同步器AbstractQueuedSynchronizer(以下簡稱同步器),是用來構建鎖或者其他同步元件的基礎框架,它使用了一個int成員變數表示同步狀態通過內建的FIFO佇列來完成資源獲取執行緒的排隊工作

    同步器的主要使用方式是繼承,子類通過繼承同步器並實現它的抽象方法來管理同步狀態,在抽象方法的實現過程中免不了要對同步狀態進行更改,這時就需要使用同步器提供的3 個方法(getState()、setState(int newState)和compareAndSetState(int expect,int update))來進行操 作,因為它們能夠保證狀態的改變是安全的。

    同步器的子類推薦被定義為自定義同步元件的靜態內部類,同步器自身沒有實現任何同步介面,它僅僅是定義了若干同步狀態獲取和釋放的方法來供自定義同步元件使用,同步器既可以支援獨佔式地獲取同步狀態,也可以支援共享式地獲取同步狀態,這樣就可以方便實現不同型別的同步元件(ReentrantLock、 ReentrantReadWriteLock和CountDownLatch等)。

    同步器是實現鎖(也可以是任意同步元件)的關鍵,在鎖的實現中聚合同步器,利用同步器實現鎖的語義。可以這樣理解二者之間的關係:鎖是面向使用者的,它定義了使用者與鎖互動的介面(比如可以允許兩個執行緒並行訪問),隱藏了實現細節同步器面向的是鎖的實現者, 它簡化了鎖的實現方式,遮蔽了同步狀態管理、執行緒的排隊、等待與喚醒等底層操作。鎖和同步器很好地隔離了使用者和實現者所需關注的領域,也就是說同步器是鎖或某個同步元件開發者所需要實現的基礎外掛,而程式設計師作為鎖和同步元件的使用者只需要呼叫開發者開發的鎖或同步元件進行使用即可

1.1 佇列同步器的介面與示例

    同步器的設計是基於模板方法模式的,也就是說,使用者需要繼承同步器並重寫指定的方法,隨後將同步器組合在自定義同步元件的實現中,並呼叫同步器提供的模板方法,而這些 模板方法將會呼叫使用者重寫的方法。

    重寫同步器指定的方法時,需要使用同步器提供的3個方法來訪問或修改同步狀態:

    (1)getState(),獲取當前同步狀態。

    (2)setState(int newState),設定當前同步狀態。

    (3)compareAndSetState(int expect,int update),使用CAS設定當前狀態,該方法能夠保證狀態設定的原子性。

    同步器可重寫的方法有:

    (1)protected boolean tryAcquire(int arg):獨佔式獲取同步狀態,實現該方法需要查詢當前狀態並判斷同步狀態是否符合預期,然後再使用CAS設定同步狀態。

    (2)protected boolean tryRelease(int arg):獨佔式釋放同步狀態,等待同步狀態的執行緒將有機會獲取同步狀態。

    (3)protected int tryAcquireShared(int arg):共享式獲取同步狀態,返回大於等與0的值則表示獲取成功,否則獲取失敗

    (4)protected boolean tryReleaseShared(int arg):共享式釋放同步狀態。

    (5)protected boolean isHeldExclusively():當前同步器是否在獨佔模式下被執行緒佔用,一般該方法表示是否被當前執行緒所獨佔。

    同步器提供的模板方法具體有三類,獨佔式釋放與獲取同步狀態,共享式獲取與釋放同步狀態,以及查詢同步佇列中等待執行緒的狀態,我們主要使用這些模板方法來實現自定義的同步元件。所謂獨佔式也就是同一時刻,只能有一個執行緒獲取或釋放同步狀態,共享式則相反。同步器提供的模板方法有:

    (1)public final void acquire(int arg):獨佔式獲取同步狀態,如果當前執行緒獲取同步狀態成功,則會返回,否則當前執行緒會進入同步佇列等待,該方法需要呼叫重寫的tryAcquire(int arg)方法

    (2) public final void acquireInterruptibly(int arg) throws InterruptedException:獨佔式獲取同步狀態,如果當前執行緒獲取同步狀態成功,則會返回,否則當前執行緒會進入同步佇列等待,但同時該方法也會響應中斷,如果執行緒在同步佇列中被中斷,則該方法會丟擲InterruptedException並返回。

    (3)public final boolean tryAcquireNanos(int arg, long nanosTimeout)
            throws InterruptedException:在acquireInterruptibly(int arg)增加了超時限制,超時未獲得同步狀態返回false,否則返回true

    (4)public final void acquireShared(int arg):共享式獲取同步狀態,如果未獲取到則進入同步佇列等待,與獨佔式不同的是,共享式可以同時有多個執行緒獲取到同步狀態。

    (5)public final void acquireSharedInterruptibly(int arg):共享式獲取同步狀態,如果當前執行緒獲取同步狀態成功,則會返回,否則當前執行緒會進入同步佇列等待,但同時該方法也會響應中斷,如果執行緒在同步佇列中被中斷,則該方法會丟擲InterruptedException並返回。

    (6)public final boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
            throws InterruptedException:在acquireSharedInterruptibly(int arg)基礎上增加了超時限制,超時未獲得同步狀態返回false,否則返回true

    (7) public final boolean release(int arg):獨佔式釋放同步狀態,該方法會在釋放同步狀態之後,喚醒同步佇列中第一個節點中的執行緒。

    (8)public final boolean releaseShared(int arg):共享式釋放同步狀態。

    (9)public final Collection<Thread> getQueuedThreads():獲取在同步佇列上等待的執行緒集合。

    依據一個獨佔鎖的實現來了解同步器工作原理,獨佔鎖,也就是同一時刻只能有一個執行緒獲取到鎖,而其他執行緒只能在同步佇列中等待,只有持有鎖的執行緒釋放鎖,後繼的執行緒才能獲取鎖,相當於synchronized鎖和ReentrantLock鎖。共享鎖的實現可以參考Semaphore執行緒工具類的內部實現。我們依據ReentrantLock鎖的內部同步器實現來作為示例:

class Mutex implements Lock {
	// 靜態內部類,自定義同步器
	private static class Sync extends AbstractQueuedSynchronizer {
		// 是否處於佔用狀態,或者說是否當前執行緒將要獲得的鎖是否被其他執行緒持有,
		//是則將狀態置為1
		protected boolean isHeldExclusively() {
			return getState() == 1;
		}

		/* 如果執行緒嘗試獲取鎖時,獲取的鎖未被其他執行緒持有(即同步器狀態為0時),
		 * 則獲取鎖並返回true,
		 * 否則(獲取的鎖被其他執行緒持有,即同步器狀態為1時)返回false
		 */
		public boolean tryAcquire(int acquires) {
			if (compareAndSetState(0, 1)) {
				setExclusiveOwnerThread(Thread.currentThread());
				return true;
			}
			return false;
		}
		//自定義方法,用該方法來加鎖
		final void lock() {
			/* 如果執行緒嘗試獲取鎖時,獲取的鎖未被其他執行緒持有(即同步器狀態為0時),
			 * 則獲取鎖並返回,否則(獲取的鎖被其他執行緒持有,即同步器狀態為1時)
			 * 將一直阻塞,直到獲取鎖
			 */
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
		// 釋放鎖,將同步器狀態設定為0
		protected boolean tryRelease(int releases) {
			if (getState() == 0)
				throw new IllegalMonitorStateException();
			setExclusiveOwnerThread(null);//設定當前持有同步器的執行緒物件
			setState(0);
			return true;
		}

		// 返回一個Condition,每個condition都包含了一個condition佇列
		Condition newCondition() {
			return new ConditionObject();
		}
	}

	// 僅需要將操作代理到Sync上即可
	private final Sync sync = new Sync();

	public void lock() {
		sync.lock();
	}

	public boolean tryLock() {
		return sync.tryAcquire(1);
	}

	public void unlock() {
		sync.release(1);
	}

	public Condition newCondition() {
		return sync.newCondition();
	}

	public boolean isLocked() {
		return sync.isHeldExclusively();
	}

	public boolean hasQueuedThreads() {
		return sync.hasQueuedThreads();
	}

	public void lockInterruptibly() throws InterruptedException {
		sync.acquireInterruptibly(1);
	}

	public boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
		return sync.tryAcquireNanos(1, unit.toNanos(timeout));
	}
}

    到了這裡,我們應該就能明白一個佇列同步器的簡單工作原理。以上面的程式碼為例,獨佔鎖Mutex是一個自定義同步元件,它在同一時刻只允許一個執行緒佔有鎖。Mutex中定義了一個靜態內部類,該內部類繼承了同步器並實現了獨佔式獲取和釋放同步狀態。在tryAcquire(int acquires)方法中,如果經過CAS設定成功(同步狀態設定為1),則代表獲 取了同步狀態,而在tryRelease(int releases)方法中只是將同步狀態重置為0。使用者使用Mutex時 並不會直接和內部同步器的實現打交道,而是呼叫Mutex提供的方法,在Mutex的實現中,以獲取鎖的lock()方法為例,只需要在方法實現中呼叫同步器的模板方法lock()即可,當前執行緒呼叫該方法獲取同步狀態失敗後會被加入到同步佇列中等待,這樣就大大降低了實現 一個可靠自定義同步元件的門檻。

    如果對”同步佇列器中的獲取與釋放同步狀態”中的同步狀態難以理解,你可以理解為是否當前同步器正在被某一個執行緒佔用的狀態,也就是是否有某個執行緒正持有通過該同步器物件實現的鎖,用0和1表示。

1.2 佇列同步器的實現分析

1.同步佇列

    同步器依賴內部的同步佇列(一個FIFO雙向佇列)來完成同步狀態的管理,當前執行緒獲取 同步狀態失敗時,同步器會將當前執行緒以及等待狀態等資訊構造成為一個節點(Node)並將其 加入同步佇列,同時會阻塞當前執行緒,當同步狀態釋放時,會把首節點中的執行緒喚醒,使其再 次嘗試獲取同步狀態。 同步佇列中的節點(Node)用來儲存獲取同步狀態失敗的執行緒引用、等待狀態以及前驅和 後繼節點。

static final class Node {
		
        /* 表明一個節點正處於共享模式 */
        static final Node SHARED = new Node();
        
        /*表明一個節點正處於獨佔模式 */
        static final Node EXCLUSIVE = null;
        
        /* 這個節點狀態值表示這個節點的執行緒等待超時或被中斷,需要從同步佇列中刪除該節點,
         * 節點進入該狀態後不會變化,並且會釋放同步狀態,使得後續節點可以執行
         * 也就是說相當於應用中一個執行緒在同步佇列中或正在執行的過程中被異常中斷或
         * 等待超時都會釋放鎖,並返回,將鎖交給其他執行緒
         */
        static final int CANCELLED =  1;
        
        /* 這個節點狀態值表示這個節點的執行緒釋放了同步狀態或者被取消,則會通知後面的節點,
         * 也就是相當於使用中執行緒的wait()/signal()機制
         */
        static final int SIGNAL    = -1;
        
        /* 相當於使用Lock鎖的Condition條件物件,表示該執行緒是否是通過Condition條件物件
         * 來實現阻塞的
         */
        static final int CONDITION = -2;
        /*
         * 表示下一次共享式同步狀態獲取將會無條件傳播下去
         */
        static final int PROPAGATE = -3;

        /*
         * waitStatus取以上CANCELLED,SIGNAL,CONDITION,PROPAGATE值,或初始值0
         */
        volatile int waitStatus;

        /*
         * 當前節點的前一個節點
         */
        volatile Node prev;

        /*
         * 連線到當前節點的下一個節點
         */
        volatile Node next;

        /*
         * 連線到當前節點被持有的執行緒
         */
        volatile Thread thread;

        /*
         * 是等待佇列的後繼節點,連線到下一個在同一個Condition條件物件上等待的節點。
         * 或者當前節點是共享式節點,則該變數是特殊值Node類中的常量SHARED,比如在Semaphore執行緒併發類中就是使用該 
         * 節點型別
         */
        Node nextWaiter;

        /*
         * Returns true if node is waiting in shared mode.
         */
        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(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;
        }
    }

    節點是構成同步佇列的基礎,同步器擁有首節點(head) 和尾節點(tail),沒有成功獲取同步狀態的執行緒將會成為節點加入該佇列的尾部。

    同步器包含了兩個節點型別的引用,一個指向頭節點,而另一個指向尾節點。 試想一下,當一個執行緒成功地獲取了同步狀態(或者鎖),其他執行緒將無法獲取到同步狀態,轉 而被構造成為節點並加入到同步佇列中,而這個加入佇列的過程必須要保證執行緒安全,因此 同步器提供了一個基於CAS的設定尾節點的方法:compareAndSetTail(Node expect,Node update),它需要傳遞當前執行緒“認為”的尾節點和當前節點,只有設定成功後,當前節點才正式 與之前的尾節點建立關聯。

    同步佇列遵循FIFO,首節點是獲取同步狀態成功的節點,首節點的執行緒在釋放同步狀態時,將會喚醒後繼節點,而後繼節點將會在獲取同步狀態成功時將自己設定為首節點。

    設定首節點是通過獲取同步狀態成功的執行緒來完成的,由於只有一個執行緒能夠成功獲取到同步狀態,因此設定頭節點的方法並不需要使用CAS來保證,它只需要將首節 點設定成為原首節點的後繼節點並斷開原首節點的next引用即可。

2.獨佔式同步狀態獲取與釋放

    (1)通過呼叫同步器的acquire(int arg)方法可以獲取同步狀態,該方法對中斷不敏感,也就是 由於執行緒獲取同步狀態失敗後進入同步佇列中,後續對執行緒進行中斷操作時,執行緒不會從同 步佇列中移出。原始碼如下:

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

    其主要邏輯是:首先呼叫自定義同步器實現的tryAcquire(int arg)方法,該方法 保證執行緒安全的獲取同步狀態,如果同步狀態獲取失敗,則構造同步節點(獨佔式 Node.EXCLUSIVE,同一時刻只能有一個執行緒成功獲取同步狀態)並通過addWaiter(Node node) 方法將該節點加入到同步佇列的尾部,最後呼叫acquireQueued(Node node,int arg)方法,使得該節點以“死迴圈”的方式獲取同步狀態。如果獲取不到則阻塞節點中的執行緒,而被阻塞執行緒的喚醒主要依靠前驅節點的出隊或阻塞執行緒被中斷來實現。

    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;
                }
            }
        }
    }

    上述程式碼通過使用compareAndSetTail(Node expect,Node update)方法來確保節點能夠被執行緒安全新增。試想一下:如果使用一個普通的LinkedList來維護節點之間的關係,那麼當一個線 程獲取了同步狀態,而其他多個執行緒由於呼叫tryAcquire(int arg)方法獲取同步狀態失敗而併發 地被新增到LinkedList時,LinkedList將難以保證Node的正確新增,最終的結果可能是節點的數量有偏差,而且順序也是混亂的。

在enq(final Node node)方法中,同步器通過“死迴圈”來保證節點的正確新增,在“死循 環”中只有通過CAS將節點設定成為尾節點之後,當前執行緒才能從該方法返回,否則,當前線 程不斷地嘗試設定。可以看出,enq(final Node node)方法將併發新增節點的請求通過CAS變 得“序列化”了。

    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);
        }
    }

    節點進入同步佇列之後,就進入了一個自旋的過程,每個節點(或者說每個執行緒)都在自省地觀察,當條件滿足,獲取到了同步狀態,就可以從這個自旋過程中退出,否則依舊留在這個自旋過程中(並會阻塞節點的執行緒),在acquireQueued(final Node node,int arg)方法中,當前執行緒在“死迴圈”中嘗試獲取同步狀 態,而只有前驅節點是頭節點才能夠嘗試獲取同步狀態,因為頭節點是成功獲取到同步狀態的節點,而頭節點的執行緒釋放了同步狀態之後,將會喚醒其後繼節點,後繼節點的執行緒被喚醒後需要檢查自己的前驅節點是否是頭節點,而且需要維護同步佇列的FIFO原則。

    圖解獨佔式獲取同步狀態流程:

    在圖中,前驅節點為頭節點且能夠獲取同步狀態的判斷條件和執行緒進入等待狀態是獲取同步狀態的自旋過程。當同步狀態獲取成功之後,當前執行緒從acquire(int arg)方法返回,如果對於鎖這種併發元件而言,代表著當前執行緒獲取了鎖。

    (2)獨佔式釋放同步狀態:當前執行緒獲取同步狀態並執行了相應邏輯之後,就需要釋放同步狀態,使得後續節點能 夠繼續獲取同步狀態。通過呼叫同步器的release(int arg)方法可以釋放同步狀態,該方法在釋 放了同步狀態之後,會喚醒其後繼節點(進而使後繼節點重新嘗試獲取同步狀態)。

    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(Node node)方法使用 LockSupport來喚醒處於等待狀態的執行緒。

    (3)總結:在獲取同步狀態時,同步器維 護一個同步佇列,獲取狀態失敗的執行緒都會被加入到佇列中並在佇列中進行自旋;移出佇列 (或停止自旋)的條件是前驅節點為頭節點且成功獲取了同步狀態。在釋放同步狀態時,同步器呼叫tryRelease(int arg)方法釋放同步狀態,然後喚醒頭節點的後繼節點。

3.共享式同步狀態獲取與釋放

    (1)共享式獲取與獨佔式獲取最主要的區別在於同一時刻能否有多個執行緒同時獲取到同步狀 態。以檔案的讀寫為例,如果一個程式在對檔案進行讀操作,那麼這一時刻對於該檔案的寫操作均被阻塞,而讀操作能夠同時進行。寫操作要求對資源的獨佔式訪問,而讀操作可以是共享式訪問,共享式同步佇列的使用與實現具體可以參考Semaphore。

    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);
        }
    }

    通過呼叫同步器的acquireShared(int arg)方法可以共享式地獲取同步狀態,在acquireShared(int arg)方法中,同步器呼叫tryAcquireShared(int arg)方法嘗試獲取同步狀 態,tryAcquireShared(int arg)方法返回值為int型別,當返回值大於等於0時,表示能夠獲取到同步狀態。因此,在共享式獲取的自旋過程中,成功獲取到同步狀態並退出自旋的條件就是 tryAcquireShared(int arg)方法返回值大於等於0。可以看到,在doAcquireShared(int arg)方法的自旋過程中,如果當前節點的前驅為頭節點時,嘗試獲取同步狀態,如果返回值大於等於0,表示 該次獲取同步狀態成功並從自旋過程中退出。

    (2)與獨佔式一樣,共享式獲取也需要釋放同步狀態,通過呼叫releaseShared(int arg)方法可以 釋放同步狀態,該方法在釋放同步狀態之後,將會喚醒後續處於等待狀態的節點。對於能夠支援多個線 程同時訪問的併發元件(比如Semaphore),它和獨佔式主要區別在於tryReleaseShared(int arg) 方法必須確保同步狀態(或者資源數)執行緒安全釋放,一般是通過迴圈和CAS來保證的,因為 釋放同步狀態的操作會同時來自多個執行緒。

    public final boolean releaseShared(int arg) {
        if (tryReleaseShared(arg)) {
            doReleaseShared();
            return true;
        }
        return false;
    }
    private void doReleaseShared() {
        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)                   // loop if head changed
                break;
        }
    }

4.獨佔式超時獲取同步狀態

    通過呼叫同步器的doAcquireNanos(int arg,long nanosTimeout)方法可以超時獲取同步狀態,即在指定的時間段內獲取同步狀態,如果獲取到同步狀態則返回true,否則,返回false。超時獲取同步狀態過程可以被視作響應中斷獲取同步狀態過程的“增強版”, doAcquireNanos(int arg,long nanosTimeout)方法在支援響應中斷的基礎上,增加了超時獲取的特性。針對超時獲取,主要需要計算出需要睡眠的時間間隔nanosTimeout,為了防止過早通知, nanosTimeout計算公式為:nanosTimeout-=now-lastTime,其中now為當前喚醒時間,lastTime為上 次喚醒時間,如果nanosTimeout大於0則表示超時時間未到,需要繼續睡眠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);
        }
    }

    該方法在自旋過程中,當節點的前驅節點為頭節點時嘗試獲取同步狀態,如果獲取成功 則從該方法返回,這個過程和獨佔式同步獲取的過程類似,但是在同步狀態獲取失敗的處理上有所不同。如果當前執行緒獲取同步狀態失敗,則判斷是否超時(nanosTimeout小於等於0表示 已經超時),如果沒有超時,重新計算超時間隔nanosTimeout,然後使當前執行緒等待 nanosTimeout納秒(當已到設定的超時時間,該執行緒會從LockSupport.parkNanos(Object blocker,long nanos)方法返回)。

    注意:如果nanosTimeout小於等於spinForTimeoutThreshold(1000納秒)時,將不會使該執行緒進行 超時等待,而是進入快速的自旋過程。原因在於,非常短的超時等待無法做到十分精確,如果 這時再進行超時等待,相反會讓nanosTimeout的超時從整體上表現得反而不精確。因此,在超 時非常短的場景下,同步器會進入無條件的快速自旋。

    獨佔式超時獲取同步狀態方法流程圖

5.總結

    同步佇列器實際上對於所有的同步元件,如ReentrantLock,Semaphore等都通過該元件進行實現。其獲取同步狀態,在獨佔式情況下可以視為獲取鎖(如ReentrantLock),在共享式情況下可以視為獲取訪問同步資源的許可權或是條件(如Semaphore)。所以,同步器的使用不僅僅是用於Lock鎖的實現,Java中提供的許多同步元件都通過同步器實現,我們甚至可以根據需要,重寫佇列同步器的方法來實現自己所需要的同步元件。


相關文章