同時11:条件キューCondition実装分析

7584 ワード

Condition


ConditionはJ.U.Cパッケージのインタフェースであり、await、signal、signalAllの3つの主要な方法を提供しています.Objectのwait、notify、notifyAllの3つのモニタメソッドと意味が一致します.
モニタメソッドはsynchronized修飾の同期体内に配置する必要があります.Conditionインタフェースも同様です.ロックの監視範囲であるlockとunlockの間のコードブロック内で呼び出す必要があります.そうしないと、IllegalMonitorStateException異常が放出されます.
小さな栗:
public class ConditionTest {
    public static class ResponseFuture {
        private final Lock lock = new ReentrantLock();
        private final Condition condition = lock.newCondition();
        private String response;

        public boolean isDone() {//  
            return response != null;
        }

        public String getResponse() throws InterruptedException{ //  
            if (!isDone()) {
                lock.lock();
                try {
                    while (!isDone()) {
                        condition.await();//   
                        if (isDone()) {
                            break;
                        }
                    }
                } finally {
                    lock.unlock();
                }
            }
            return response;
        }

        public void done(String response) {//  
            lock.lock();
            try {
                this.response = response;
                condition.signal();//  
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        final ResponseFuture responseFuture = new ResponseFuture();
        new Thread(new Runnable() {//  
            public void run() {
                System.out.println(" ");
                try {
                    //  , 
                    System.out.println(responseFuture.getResponse());
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        
        
        new Thread(new Runnable() {//  
            public void run() {
                try {
                    Thread.sleep(10000);//  
                }catch (InterruptedException e) {
                    e.printStackTrace();
                }
                //  
                responseFuture.done(" ");
            }
        }).start();
    }
}

ConditionObjec


Conditionインタフェースの実装ConditionObjectは、AbstractQueuedSynchronizerの内部がAQS()にある.
public class ConditionObject implements Condition, 
                java.io.Serializable {
    private static final int REINTERRUPT = 1;
    private static final int THROW_IE = -1;
    /**   */
    private transient Node firstWaiter;
    /**   */
    private transient Node lastWaiter;
    public ConditionObject() {}
    ... ...
}

げんり
ブロックスレッドのキューとスケジューリングを行うためのチェーンテーブルNodeキューを維持します.条件キューと呼ばれます.
スレッドはconditionを呼び出した.await()は、スレッドが持つロックを解放し、Nodeが条件キューに追加されてからスレッドを一時停止するように構成されます.
他のスレッドはconditionを呼び出した.Signal()の後、条件キューで最初に待機していたノードをAQS同期キューに移行し、AQS同期キューでロック取得のスケジューリングに参加し、ロックが取得されるとスレッドが起動する.
awaitプロセス
// s1
public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();//    
            Node node = addConditionWaiter();//   s2
            int savedState = fullyRelease(node);//   s3
            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)
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
        }

s 1:スレッドは中断せずに入列を開始し、s 2に移行し、そうでなければ直接異常を投げ出す.エントリは、現在のスレッドが保持するロックをs 3に解放することに成功した.isOnSyncQueue(node)は、ノードがAQS同期キューにいるかどうかを判断し、いない場合は、そのスレッドがロックの競合に参加する資格がないことを示し、現在のスレッドを一時停止します.NodeがAQS同期キューに参加するか、nodeに対応するスレッドが中断されるまで、while{}ループを終了しません.checkInterruptWhileWaitingメソッドは、割り込みが発生したタイミングをチェックします.signalの前に発生した割り込みはTHROWを返します.IEは、InterruptedException signalを投げ出した後に発生した割り込みがREINTERUPTに戻ることを示し、記録割り込み状態がそうでなければ0に戻ることを示し、割り込みが発生してもtransferAfterCancelledWaitメソッドはnodeをAQS同期キューに移行することを示す.ACquireQued()メソッドを呼び出してAQS同期キューでロックを競合させ、ロックを取得してから割り込みの処理を開始し、reportInterruptAfterWaitメソッドではinterruptModeに基づいて例外を放出するか記録するかを選択します.
// s2
private Node addConditionWaiter() {
            Node t = lastWaiter;
            //  CONDITION, 
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //  
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

s 2:末尾ノードtが空でない場合、または状態がCONDITIONでない場合、unlinkCancelledWaiters()メソッドが呼び出されます.これは、ノードから取り外されるので状態がCONDITIONではないノードを先頭から外すことを目的としたループメソッドです.t=nullは、現在のキューか空のキューかを示し、nodeをヘッダノードfirstWaiterに割り当てます.そうしないと、nodeは末尾にチェーンされます.エンドノードlastWaiterをnodeに向け、s 1を返します.
// s3
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;
        }
    }

s 3:release()メソッドで現在のロックを解放します.これは排他ロックの解放メソッドです.そのため、Conditionオブジェクトは排他ロックでの使用のみをサポートします.リリースが完了したらs 1に戻ります.
Signalプロセス
public final void signal() {
    if (!isHeldExclusively())//   
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;// 
    if (first != null)// 
        doSignal(first);
}
private void doSignal(Node first) {
    do {
        if ((firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    } while (!transferForSignal(first) && (first = firstWaiter) != null);
}

s 1:first.nextWaiter=nullはキューが空であることを示し、エンドノードlastWaiterも空にします.first.nextWaiter=nullはヘッダノードをキューから取り外します.s 2に移行してノード転送を行う.転送に成功するとループが終了し、転送に成功せず、キューに待機しているノードが次のノードを転送し続けます.
// s2
final boolean transferForSignal(Node node) {
    // Node.CONDITION  0,
    // CONDITION 
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;
    Node p = enq(node);// 
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

s 2:CASはノード状態をCONDITIONから0に設定する.Enq(node)は、AQSにおける同期キューのエンキュー方法であり、ノードを同期キューに入れる.ws>0は、ノードがキャンセルされた(中断する可能性がある)か、ノードの状態をSIGNALに設定できなかった場合、スレッドを直接起動することを示す.起動されたスレッドは、「awaitプロセスs 1」でロックの競合に参加したり、処理が中断したりします.
SignalAllはsignalロジックと同様に,ループでtransferForSignal(first)を実行するだけで,条件キュー内のノードを順次すべて同期キューに移行する.

まとめ:


1:ConditionはReentrantLock、ReentrantReadWriteLockなどの排他ロックでのみ使用できる.writerLock.ReentrantReadWriteLockなどの共有ロックでは使用できません.readerLock.
2:Conditionのメソッドは、ロックの監視範囲内、すなわちlockとunlockの間のコードブロック内で呼び出す必要があります.
コードワードは容易ではありませんて、転載して原文の接続を保留してくださいhttps://www.jianshu.com/p/e72b43ebd788