並行プログラミングのソースコード解析の条件変数条件

Fanger魏コードまたは次のスキャンの公共マイクロチャネル番号を検索し菜鸟飞呀飞、あなたは、マイクロチャネル公共数に焦点を当て、よりを読むことができるSpring源码分析、とJava并发编程の記事。

マイクロチャンネル公衆数

AQSキュー同期がチューブモデル、チューブモデルによって達成され、我々は2つのキューを述べた:入口等待队列条件变量等待队列AQS内に同步队列、対応する管モデル入口等待队列条件等待队列管モデルに対応します条件变量等待队列AQS同期キューの実装についての設計原理とソースは、これら二つの記事を読むことができます:設計原理は、シンクロナイザ(AQS)のキューおよびキュー・シンクロナイザ(AQS)ソースコード解析を今日AQS待ち行列の設計原理とソースコードを詳細に分析されます。(チューブの導入の記事を参照することができる:チューブ:並行プログラミングの基礎を

簡単な紹介

  • フィールド内の2つの同時の問題に対処する必要があります。相互に排他的な共有リソースにアクセスするための唯一つのスレッドを許可同じ時間を参照して、それAQS同期キューは、私たちが解決に役立っています。同期は、スレッド間の通信とコラボレーションは、次いで、AQSは、同期の問題を解決する方法である方法を指しますか?答えは、今日のヒーローです互斥同步Condition
  • 条件はJUCのパケットインタフェースであり、それはクラスが達成ConditionObjectキュー同期AbstractQueuedSynchronizer(後AQSと呼ぶ)は、内部クラスです。ロックと一緒に使用しLock.newCondition()作成インスタンス。
  • (通知)(待つ):ネイティブオブジェクトクラスの三つの方法があり 、のnotifyAll()、 同期のキーワードを使用するために設計されて、スレッド間の通信を実現するためにそれを使用するには。条件および提供するawait()、signal()、signalAll()三つの方法に対応する3つの方法を、クラスオブジェクト、前のスレッドに通信するために使用されるが、いくつかの違いが関数に存在し、それらは共通の方法を使用しています。具体的な違いは、テーブルを参照することができます。(からの表の「Java並行プログラミングの技術、」本の第5章、第6節)
比較項目 オブジェクト 調子
前提を使用してください ロックを同期するために使用します ロックを取得するには、ロック()メソッドを使用してインスタンスをロック
使用 object.notifyはObject.wait()、()等 ロックインターフェイスインスタンスオブジェクトが作成されて使用する必要があり、Lock.newCondition()
待ち行列の数 サポートA 複数の、新しいロックインスタンスの使用をサポートすることができ、よりコンディションすることができ
待ちキューにスレッドを割り込みに応答するかどうか サポートしていません。 サポート
タイムアウト待ち サポート サポート
時間に未来のポイントにスレッドリリースロックを待機した後 サポートしていません。 サポート
キュー内で待機しているスレッドを覚まします サポートは、()に通知します サポート、信号()
キュー内で待機中のすべてのスレッドを覚まします サポートのnotifyAll() サポート、signalAll()

列を設計する方法で待っAQS

データの構造

  • AQS同期データ構造は、ノード要素が形成されている二重連結リスト待ち行列であり、同様に、一方向のみリンクリスト、ノード要素にリンクされたリストのキューが形成されます。では、キューシンクロナイザ(AQS)設計の原則この記事属性内のノードが導入されており、今日では再び性質や用途を確認しています。
プロパティ名 効果
ノードの前 同期キュー、かつての現在のノード、現在のノードが同期されている場合は、最初のノードのキューは、その後、属性がnullの前で
ノードの横 ノードが同期キューである場合は、キューの同期、現在のノードの後に​​、テール電流ノードは、次のプロパティがnullです
ノードのスレッド 現在のスレッドがロックを取得する場合は、ノードの現在のスレッドがチームの最初の同期キューの中の特定を表し、現在のノードが表すスレッド、およびスレッドプロパティは、あなたがそれは仕様によるAQSである、nullに設定したい理由として、nullです。
int型waitStatus 現在のスレッドの状態を待って、値の5種類があります。0 -1待ち状態で、現在のスレッドを表す-2キュー内のノードについて、1の初期値は、スレッドが取り消されることを示して表し、共有状態では-3同期は無条件ダウン伝播する取得します
ノードnextWaiter 待ち行列、ノードの次のノード
  • AQSは、等待队列ノードnode使用して実装されたnextWaiter属性とwaitStatus前記達成するためにプロパティをwaitStatus=-2、それが表します线程是处于等待队列中(前のページと次の同期キューは、二重リンクリストを実装するプロパティに依存しています)。:条件は、2つのプロパティを含んでいるfirstWaiterlastWaiter、それぞれ、最初のチームとキューの末尾を。キューは、最初に出(FIFO)の最初の原則に従うことを待っています。下の図は、条件待ち行列を示しています。

キュー待機

原則

  • 原則同期ロックを達成チューブモデルを通じて達成しますが、ロックを達成同期され、唯一のキューをサポートし、条件は、複数のキューをサポートすることができます。AQSでは、同期キューとキューは、以下の模式図です。

同期キューおよび待機キュー

  • その後、現在のスレッドが第1ノードノードにカプセル化しているだろうのawaitの条件()メソッドは、ノードの呼び出し、ロックによってロック・スレッドを取得した場合添加到等待队列、その後、ロックを解除し、最後に呼び出すLockSopport.park()方法を自分自身をハングします。なお、図の模式図として使用することができます。

await()概略

  • 条件信号を呼び出すとき()メソッドは、最初のキュー削除firstWaiterノードを、次にfirstWaiterノードに追加同步队列します。以下の図。

信号()概略

  • あなたがsignalAllの条件()メソッドを呼び出している場合は、キューの状態でお待ちしております所有Node节点移到同步队列中

ソースコード解析

私は、キューの上記のデータ構造と実装の原則は、その後、ソース固有の実装を結合するために見ていることを高く評価しました。次は待つ()メソッドと信号()メソッドのソースコードを分析します。

待つ()メソッド

Condition.await()メソッドを呼び出すときは、()メソッドでは内部クラスConditionObjectのAbstractQueuedSynchornizerを待つために呼び出します。次のようにメソッドのソースです。

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)) {
        // 如果节点不在同步队列中,将当前线程park
        LockSupport.park(this);
        /**
         * 当被唤醒以后,接着从下面开始执行。醒来后会判断自己在等待过程中有没有被中断过。
         * checkInterruptWhileWaiting()方法返回0表示没有被中断过
         * 返回-1表示需要抛出异常
         * 返回1表示需要重置中断标识
         */
        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);
}
复制代码
  • await()メソッドでは、現在のスレッドが最初に呼び出しますaddConditionWaiter()彼らは、ノードの中にカプセル化された後、方法を、待ちキューに追加されました。その後、呼び出しfullyRelease()ていない待ちキューに、自分自身をオンにした場合の方法は、待ちキュー内の現在のスレッドかどうかを判断することにより、その後、ロックを解除しますpark
  • addConditionWaiter()メソッドでは、主に二つのことをしました。:ノードが解除状態、すなわちwaitStatus =ノード1ノード内のキューの削除を待機する;第二:コンディションように自身が、待ち行列に追加lastWaiter現在のノードと同じ属性は、スレッドを表します。(注:キューが初期化されていない場合は、この時点では、最初に初期化されます)。次のようにソースaddConditionWaiter()メソッドです。
/**
 * Adds a new waiter to wait queue.
 * @return its new wait node
 * 将当前线程加入到等待队列中
 */
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    // 创建一个节点,节点的waitStatus等于-2
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    // 如果等待队列还没有初始化,即等跌队列中还没有任何元素,那么此时firstWaiter和lastWaiter均为null
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node; // 令旧的队尾节点nextWaiter属性等于当前线程的节点,这样就维护了队列的前后关系
    lastWaiter = node;
    return node;
}
复制代码
  • 以下のためfullyRelease()の方法、メソッド名は、その役割があると見ています完全释放锁なぜここに完全にロックを解放?再入国ロックのため、锁可能被重入了多次,此时同步变量state的值大于1時間、およびので、ここではfullyRelease(命名()メソッドは、値が0に減少した状態する必要があるので、インクルードがロックアウト現在のスレッドのリリースを聞かせするために、)のawaitを呼び出します。fullyRelease()は、最終的にはAQSを呼び出すrelease()ロックを解除する方法を。次のようにソースfullyRelease()メソッドです。
final int fullyRelease(Node node) {
    boolean failed = true;
    try {
        // 获取同步变量state的值
        int savedState = getState();
        // 释放锁,注意此时将同步变量的值传入进去了,如果是重入锁,且被重入过,那么此时savedState的值大于1
        // 此时释放锁时,会将同步变量state的值减为0。(通常可重入锁在释放锁时,每次只会将state减1,重入了几次就要释放几次,在这里是一下子全部释放)。
        if (release(savedState)) {
            failed = false;
            return savedState;
        } else {
            // 当线程没有获取到锁时,调用该方法会释放失败,会抛出异常。
            throw new IllegalMonitorStateException();
        }
    } finally {
        if (failed)
            node.waitStatus = Node.CANCELLED;
    }
}
复制代码
  • 続いてロック解除完了のawait()メソッドでは、isOnSyncQueue()現在のスレッドの判定が节点是不是在同步队列isOnSyncQueue()メソッドは、同期キューを表すノード、場合には、現在のノードは、同期キューにないことを示し、falseを返す場合に真を返します。falseを返す場合は、会进入到while循环中この時間は呼び出しますLockSupport.park()、メソッドを現在のスレッドが中断してみましょう。スレッドがロックをつかむために、スレッドがある場合ロックは、ロック同期キューをつかむために目を覚ますだろう解放した後により、現在のスレッドに、現在のスレッド同期キューは、確かにので、この場合には、(最初のノード同期キュー変更)ないisOnSyncQueue ()メソッドは、偽高い確率を返し、したがって一方(メソッド)を入力します。キュー内の現在のスレッドの同期場合、またはパーク()(同期キューに起床後(信号として現れる)またはsignalAll()メソッドは、同期キューにノードを追加)から起床後に実行されますその後ろの()メソッドのロジック待ってacquireQueued()、それがブロックされて取得していない、返すようになります場合は、ロックを取得しようとすることです方法を、。
  • isOnSyncQueue()ソースコードと以下のコメント。
final boolean isOnSyncQueue(Node node) {
    // 如果节点的waitStatus=-2时,节点肯定不在同步队列中,因为只有在等待队列时,才会为-2。
    // 如果节点的前驱节点为空时,有两种情况:
    // 1. 当前节点是同步队列的首节点,首节点是已经获取到锁的线程,可以认为线程不在同步队列中
    // 2. 当前节点在等待队列中,等待队列中的节点在创建时,没有给prev属性赋值
    if (node.waitStatus == Node.CONDITION || node.prev == null)
        return false;
    // 在上一个判断的前提条件下,如果后置节点不为空,那么当前节点肯定在同步队列中。
    if (node.next != null) // If has successor, it must be on queue
        return true;
    /*
     * node.prev can be non-null, but not yet on queue because
     * the CAS to place it on queue can fail. So we have to
     * traverse from tail to make sure it actually made it.  It
     * will always be near the tail in calls to this method, and
     * unless the CAS failed (which is unlikely), it will be
     * there, so we hardly ever traverse much.
     */
    // 以上情况均不属于,那么就从同步队列的尾部开始遍历,找到同步队列中是否含有node节点
    return findNodeFromTail(node);
}
复制代码
  • 待っています()メソッドについては、三つのステップで要約:1.待ちキューに自分自身を追加するために、2.ロックを解除し、3.彼の公園()。条件インターフェースでのawait()メソッドがオーバーロードされたいくつかを提供し、それらはそのような)割り込みや他の機能への応答を待たずに、待っているが、論理と実質的のawait(タイムアウトなどの機能の一部でawaitを()メソッドに基づいて追加されます以下のように、興味を持っている友人は、下の勉強に行くことができます。

信号()メソッド

  • お問い合わせの際condition.signal()方法に内部クラスAbstractQueuedSynchornizerに呼び出しますConditionObject的signal()方法次のようにメソッドのソースです。
public final void signal() {
    // 先判断当前线程有没有获取到锁,如果没有获取到锁就来调用signal()方法,就会抛出异常
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignal(first);
}
复制代码
  • 信号に()メソッドは、現在のスレッドがない場合は、例外をスローし、ロックの所有者であるか否かを判定する。現在のスレッドがロックを保持しているスレッドに等しいかどうかを決定するために特定のロジックを実行するこれらの方法の前にあるため、現在のスレッドがロックを取得する:それはコールが(待つ理由である)と信号()、signalAll()メソッドは、その提供しました。それがコードから分かるように、特定の論理信号()メソッドであるdoSignal()方法で実装します。次のようにソースdoSignal()メソッドです。
private void doSignal(Node first) {
    do {
        // 令firstWaiter等于条件等待队列中的下一个节点。
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
        /**
         *调用transferForSignal()方法,是将节点从条件等待队列中移到同步队列中.
         * 当transferForSignal()返回true时,表示节点被成功移到同步队列了。返回false,表示移动失败,当节点所表示的线程被取消时,会返回false
         * 当transferForSignal()返回true时,do...while循环结束。返回false时,继续。为什么要这样呢?
         * 因为当transferForSignal()返回false表示条件等待队列中的,队列的头结点的状态时取消状态,不能将它移到同步队列中,随意需要继续从条件等待队列找没有被取消的节点。
         */
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}
复制代码
  • アクションdoSignalは、()メソッドは、キューのヘッドノードを待つことである从等待队列中移除し、次に呼び出すtransferForSignal()方法加入到同步队列、()を真transferForSignalを返し、状況がする唯一の1、偽の障害が動きを示し返し、ノードが正常に同期キューに移動されていることを示し失敗した移動、そのスレッドがキャンセルされます。次のようにソースtransferForSignal()メソッドです。
/**
 * Transfers a node from a condition queue onto sync queue.
 * Returns true if successful.
 * @param node the node
 * @return true if successfully transferred (else the node was
 * cancelled before signal)
 * 将节点从条件等待队列移到同步队列
 */
final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    // 将节点的waitStatus的值从-2改为-1。这里如果出现CAS失败,说明节点的waitStatus值被修改过,在条件等待队列中,只有当线程被取消后,才会去修改waitStatus
    if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
        return false;

    /*
     * Splice onto queue and try to set waitStatus of predecessor to
     * indicate that thread is (probably) waiting. If cancelled or
     * attempt to set waitStatus fails, wake up to resync (in which
     * case the waitStatus can be transiently and harmlessly wrong).
     */
    // 当加入到同步队列后,需要将当前节点的前一个节点的waitStatus设置为-1,表示队列中还有线程在等待
    // 如果前驱节点的waitStatus大于0表示线程被取消,需要将当前线程唤醒
    // 或者修改前驱节点的waitStatus是失败,也需要去唤醒当前线程
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}
复制代码
  • 信号()方法:同期キューに待ちキューノード移動のヘッド。見つけることができ慎重な友人は、呼び出し信号()メソッドは、現在のスレッドで呼び出されるまで、そのスレッドは呼び出し信号()メソッドを終了したときに、現在のスレッドが実行され、ビジネス・ロジックを所有する、現在のスレッドのリリースにロックをしませんlock.unlock()メソッドは、ロックを解除します。

signalAll()メソッド

  • signalAll()作用の方法があり唤醒等待队列中的所有节点、信号()メソッドは、最初のノード待ち行列を覚まします。呼び出すときcondition.signalAll()メソッドは、()メソッドでAbstractQueuedSynchornizer内部クラスConditionObjectのsignalAllを呼び出します。次のようにメソッドのソースです。
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        // 核心逻辑在doSignalAll()方法
        doSignalAll(first);
}
复制代码
  • signalAll()メソッドを呼び出すdoSignalAll()方法を。次のようにソースdoSignalAll()メソッドです。
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    // 通过do...while循环,遍历等待队列的所有节点
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        // transferForSignal()方法将节点移到到同步队列
        transferForSignal(first);
        first = next;
    } while (first != null);
}
复制代码
  • 見つけることができる、doSignalAll()メソッドのdo...whileループでは、通過するすべてのノードは、キュー、サイクル呼び出しを待つtransferForSignal()方法等待队列中的节点全部移动到同步队列

概要

  • 本論文では、待ちキュー機能のAQSを説明し、類似点と相違点法の対応するオブジェクトと比較します。その後のawait()メソッドと信号()メソッドの原理を示す模式図で組み合わせるキューデータ構造解析を待ちます。最後に、これらの3つの方法の特定の、詳細な分析を待つ()、信号()、signalAll()を達成するために、ソース。
  • 開発者が唯一する必要が使用することは非常に便利な条件は、Lock.newCondition()条件インスタンスメソッド、作成することができ多次调用Lock.newCondition()メソッドを、それが作成されます多个条件等待队列
  • アプリケーションの状態広い範囲が、私たちは多くの場合、有界キューの接触がLinkedBlockingQueue条件によって達成され、興味を持っている友人は、下のソースコードを読んで行くことができます。

関連勧告

マイクロチャンネル公衆数

おすすめ

転載: juejin.im/post/5dc04a136fb9a04aa333bfb8