Java併發——Condition

午夜12點發表於2019-03-03

簡介

和基類Object的監視器方法一樣,Condition介面也提供了類似方法,配合Lock可以實現等待/通知模式

Object的監視器方法與Condition介面對比:

對比項 Object監視器方法 Condition
前置條件 獲取物件的鎖 呼叫Lock.lock()獲取鎖
呼叫Lock.newCondition()獲取Condition物件
呼叫方式 直接呼叫
如:object.wait()
直接呼叫
如:condition.wait()
等待佇列個數 一個 多個
當前執行緒釋放鎖並進入等待狀態 支援 支援
當前執行緒釋放鎖並進入等待狀態,在等待狀態中不響應中斷 不支援 支援
當前執行緒釋放鎖並進入超時等待狀態 支援 支援
當前執行緒釋放鎖並進入等待狀態到將來的某個時間 不支援 支援
喚醒等待佇列中的一個執行緒 支援 支援
喚醒等待佇列中的全部執行緒 支援 支援

Condition實現

在介紹AQS同步器時知道其維護了一個同步佇列,其實還維護了多個等待佇列,兩佇列均為FIFO佇列,F4看Condition實現類,可以發現其只有這兩個類(這兩個類區別在於state同步狀態一個int,一個long)

Java併發——Condition

等待佇列

具體來看下Condition實現類AQS中內部類ConditionObject


    public class ConditionObject implements Condition, java.io.Serializable {
        private static final long serialVersionUID = 1173984872572414699L;
        // 頭節點
        private transient Node firstWaiter;
        // 尾結點
        private transient Node lastWaiter;

        public ConditionObject() { }
        ...
    }    
複製程式碼

結構如圖(單向佇列):

Java併發——Condition

await()

await()方法過程相當於同步佇列的首節點(獲取了鎖的節點)移動到Condition的等待佇列中


        public final void await() throws InterruptedException {
            // 判斷當前執行緒是否中斷
            if (Thread.interrupted())
                throw new InterruptedException();
            // 當前執行緒加入等待佇列    
            Node node = addConditionWaiter();
            // 釋放同步狀態
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            // 判斷此節點是否在同步佇列中,若不在直至在同步佇列為止
            while (!isOnSyncQueue(node)) {
                // 阻塞當前執行緒
                LockSupport.park(this);
                // 若執行緒已經中斷則退出
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
            // 競爭同步狀態
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }
複製程式碼

先是判斷執行緒是否中斷,若中斷直接丟擲異常,否則呼叫addConditionWaiter()將執行緒包裝成節點加入等待佇列


        private Node addConditionWaiter() {
            // 獲取等待佇列的尾節點
            Node t = lastWaiter;
            // 若尾節點狀態不為CONDITION,清除節點
            if (t != null && t.waitStatus != Node.CONDITION) {
                // 清除等待佇列中所有狀態不為CONDITION的節點
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            // 將當前執行緒包裝成Node節點
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            // 尾插節點
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            // 將節點置為尾節點    
            lastWaiter = node;
            return node;
        }
複製程式碼

從原始碼來看將節點加入等待佇列並沒有使用CAS,因為呼叫await()方法的執行緒必定是獲取了鎖的執行緒,即此過程是由鎖來保證執行緒安全,成功加入等待佇列後,呼叫fullyRelease()釋放同步狀態


    final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            // 獲取同步狀態
            int savedState = getState();
            // 釋放鎖
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
                node.waitStatus = Node.CANCELLED;
        }
    }
複製程式碼

呼叫AQS的模板方法release()方法釋放同步狀態並且喚醒在同步佇列中頭結點的後繼節點引用的執行緒,如果釋放成功則正常返回,否則丟擲異常。隨後呼叫isOnSyncQueue()判斷節點是否在同步佇列


    final boolean isOnSyncQueue(Node node) {
        // 若狀態為CONDITION或者前驅節點為null
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        // 若後繼節點不為null,表明節點肯定在同步佇列中    
        if (node.next != null) // If has successor, it must be on queue
            return true;
        // 從同步佇列尾節點找節點    
        return findNodeFromTail(node);
    }
複製程式碼

若節點不在同步佇列會一直在while迴圈體中,當此執行緒被中斷或者執行緒關聯的節點被移動到了同步佇列中(即另外執行緒呼叫的condition的signal或者signalAll方法)會結束迴圈呼叫acquireQueued()方法獲取,否則會在迴圈體中通過LockSupport.park()方法阻塞執行緒

await()方法示意:

Java併發——Condition

signal()/signalAll()

  • signal()
  • 
        public final void signal() {
            // 判斷當前執行緒是否為獲取鎖的執行緒
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            // 喚醒條件佇列中的第一個節點
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
    複製程式碼

    isHeldExclusively()方法需要子類重寫,其目的在於判斷當前執行緒是否為獲取鎖的執行緒

    
        protected boolean isHeldExclusively() {
            throw new UnsupportedOperationException();
        }
    複製程式碼

    doSignal

    
            private void doSignal(Node first) {
                do {
                    // 將頭節點從等待佇列中移除
                    if ( (firstWaiter = first.nextWaiter) == null)
                        lastWaiter = null;
                    first.nextWaiter = null;
                } while (!transferForSignal(first) &&
                         (first = firstWaiter) != null);
            }
    複製程式碼

    doSignal()將頭節點移出等待佇列,再呼叫transferForSignal()方法將節點新增到同步佇列中

    
        final boolean transferForSignal(Node node) {
            // 將node節點狀態由CONDITION  CAS置為初始狀態0
            if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
                return false;
            // 將node節點尾插同步佇列,返回插入後同步佇列中node節點的前驅節點
            Node p = enq(node);
            int ws = p.waitStatus;
            if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
                LockSupport.unpark(node.thread);
            return true;
        }
    複製程式碼

    signal()方法示意:

    Java併發——Condition
  • signalAll()
  • 
            private void doSignalAll(Node first) {
                lastWaiter = firstWaiter = null;
                // 將等待佇列中節點從頭節點開始逐個移出等待佇列,新增到同步佇列
                do {
                    Node next = first.nextWaiter;
                    first.nextWaiter = null;
                    transferForSignal(first);
                    first = next;
                } while (first != null);
            }
    複製程式碼

    signalAll()方法相當於對等待佇列中的每個節點均執行一次signal()方法,將等待佇列中所有節點全部移動到同步佇列中,並喚醒每個節點的執行緒

    感謝

    《java併發程式設計的藝術》

    相關文章