1. 程式人生 > >鎖機制(四)

鎖機制(四)

http aqs arch 可重入鎖 Owner lan 計數 div monitor

本小節介紹鎖釋放Lock.unlock()。

Release/TryRelease

unlock操作實際上就調用了AQS的release操作,釋放持有的鎖。

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(arg)操作,此操作裏面總是嘗試去釋放鎖,如果成功,說明鎖確實被當前線程持有,那麽就看AQS隊列中的頭結點是否為空並且能否被喚醒,如果可以的話就喚醒繼任節點(下一個非CANCELLED節點,下面會具體分析)。

對獨占鎖,java.util.concurrent.locks.ReentrantLock.Sync.tryRelease(int) 釋放鎖的過程。

protected final boolean tryRelease(int releases) {
    int c = getState() - releases;//將AQS狀態位減少要釋放的次數(對於獨占鎖而言總是1
    
if (Thread.currentThread() != getExclusiveOwnerThread())//判斷持有鎖的線程是否是當前線程 throw new IllegalMonitorStateException();//如果不是就拋出IllegalMonitorStateExeception() boolean free = false; if (c == 0) { //如果剩余的狀態位0(也就是沒有線程持有鎖) free = true;// setExclusiveOwnerThread(null);//當前線程是最後一個持有鎖的線程,清空AQS持有鎖的獨占線程 } setState(c);//將剩余的狀態位寫回AQS,如果沒有線程持有鎖就返回true,否則就是false
return free; }

參考上一節的分析就可以知道,這裏c==0決定了是否完全釋放了鎖。由於ReentrantLock是可重入鎖,因此同一個線程可能多重持有鎖,那麽當且僅當最後一個持有鎖的線程釋放鎖是才能將AQS中持有鎖的獨占線程清空,這樣接下來的操作才需要喚醒下一個需要鎖的AQS節點(Node),否則就只是減少鎖持有的計數器,並不能改變其他操作。

tryRelease操作成功後(也就是完全釋放了鎖),release操作才能檢查是否需要喚醒下一個繼任節點。這裏的前提是AQS隊列的頭結點需要鎖(waitStatus!=0),如果頭結點需要鎖,就開始檢測下一個繼任節點是否需要鎖操作。

在上一節中說道acquireQueued操作完成後(拿到了鎖),會將當前持有鎖的節點設為頭結點,所以一旦頭結點釋放鎖,那麽就需要尋找頭結點的下一個需要鎖的繼任節點,並喚醒它。

private void unparkSuccessor(Node node) {
        //此時node是需要是需要釋放鎖的頭結點

        //清空頭結點的waitStatus,也就是不再需要鎖了
        compareAndSetWaitStatus(node, Node.SIGNAL, 0);

        //從頭結點的下一個節點開始尋找繼任節點,當且僅當繼任節點的waitStatus<=0才是有效繼任節點,否則將這些waitStatus>0(也就是CANCELLED的節點)從AQS隊列中剔除  
       //這裏並沒有從head->tail開始尋找,而是從tail->head尋找最後一個有效節點。
       //解釋在這裏 http://www.blogjava.net/xylz/archive/2010/07/08/325540.html#377512

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

公平鎖和非公平鎖只是在獲取鎖的時候有差別,其它都是一樣的。

在上面非公平鎖的代碼中總是優先嘗試當前是否有線程持有鎖,一旦沒有任何線程持有鎖,那麽非公平鎖就霸道的嘗試將鎖“占為己有”。如果在搶占鎖的時候失敗就和公平鎖一樣老老實實的去排隊。

也即是說公平鎖和非公平鎖只是在入AQSCLH隊列之前有所差別,一旦進入了隊列,所有線程都是按照隊列中先來後到的順序請求鎖。

鎖機制(四)