条件のソースのAQS-深い理解

序文

やがて共有ReetrantLockの実現公正を達成するために、詳細な分析ReentrantLockのフェアロックと非ロックソースを今して振り返ってみると、AQSは、より深い理解と正確なを持っているため、その後、次の記事前に更新。今日達成するためのもう一つの重要な使用AQS JUCツールを共有しますConditionパートが所定の位置に理解している場合、これはCondition学ぶのが難しいものではありません-

我々は、すべてを知る必要があるObjectメソッドのモニターの一部:wait()notify()notifyAll()スレッドは、待ち行列待ちの状態に、)コール待機を(満たしていない場合、リソースの必要性は、あなたが特定の条件を満たしている必要がある場面で満たされており、この状態は、この操作のための別のスレッドB、スレッドBのリソースで作成しました、条件が満たされた条件を満たすと、スレッドBは、待機中のスレッドのキューを通知します。このプロセスは、あなたがメソッドのモニターロックを呼び出す取得する必要がなぜあること、共有データを発見するマルチスレッド動作を必要とするだろう。さらに、モニターを運ぶオブジェクトは、それが唯一の同期キュー、キューの条件を含めることができます。条件は、上記のモデルの別の実装であるなど、より豊富なサポートを特徴:

  • 同期キューは、複数のキューを持つことができます

  • 割り込み応答処理を終了するのを待つを待つことができません

  • ミートロック待機時間を取得するために待っている指定された条件

条件の使用

我々の共通のBlockingQueueの複数のスレッド間通信の仕組みに属し条件は、条件が達成基づいています。我々はまた、多くの場合、我々は、RPCクライアント側でネッティーIOを使用するなど、BlockingQueueのは、非同期RPC通信フレームワークで使用される参照ネッティーそのものとしての非ブロッキング書き込み操作を、そして、スレッドですべての業務を達成することが可能である同期ブロッキング呼び出しのビジネス要件は、結果を得ますネッティークライアントはBlockingQueueのに応答データ、パディングを受け、ウェイクアップ要求のスレッドがブロックされた後などBlockingQueueの、上の書き込みデータブロックの後に。

文書内の特定の例を使用して、条件JDKは簡単なのBlockingQueueを実装する方法です。

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class BoundedBuffer {
    final Lock lock = new ReentrantLock();
    // 创建Condition一定依赖Lock实例
    final Condition notFull = lock.newCondition();
    final Condition notEmpty = lock.newCondition();

    final Object[] items = new Object[100];

    int putptr, takeptr, count;

    public void put(Object x) throws InterruptedException {
        lock.lock();
        try {
		// 生产者线程发现队列满了,无法继续生产,只能在notFull条件上排队等待
		// while循环为为了防止假唤醒
            while (count == items.length)
                notFull.await(); 
            items[putptr] = x;
            if (++putptr == items.length) putptr = 0;
            ++count;
			// 生产成功,通知等待notEmpty条件的线程来消费
            notEmpty.signal(); 
        } finally {
            lock.unlock();

        }

    }

    public Object take() throws InterruptedException {
        lock.lock();
        try {

		// 消费者线程如果发现没有数据可消费,只能排队等待在notEmpty条件上
            while (count == 0)
                notEmpty.await(); 
            Object x = items[takeptr];
            if (++takeptr == items.length) takeptr = 0;
            --count;
			//每次被消费者线程消费一个都会发个通知,告诉等待notFull条件的线程
            notFull.signal(); 
            return x;
        } finally {
            lock.unlock();
        }
    }
}

复制代码

条件達成

全体の構造

26014d69c40148b88b15aa63cc2a9f00-image.png

状態同期キューとキュー複数の条件、条件が満たされない場合、実行のスレッドを含む全体として全体の待ち行列モデルは、のawait()メソッドの呼び出しは、スレッドが満たされると、ノードnode条件を追加するために、状態キューにパッケージされます条件、状態キューは(他のスレッド信号になります)の通知後、ロックをつかむために、ヘッドノードの同期キューを招待し、ロックは継続し、待機から()メソッドが終了をつかむだろう。条件状態キューに各インスタンス対応し、キューが実現状態でConditionObject、単独でリンクされたリストは、将来的にキューがキューにブロッキング状態から転送されるべきであるという理由だけであれば、各ノードは、ノードのインスタンスである、内部保持。


 public class ConditionObject implements Condition, java.io.Serializable {

        /** First node of condition queue. */

        private transient Node firstWaiter;

        /** Last node of condition queue. */

        private transient Node lastWaiter;

复制代码

操作方法の各呼び出しの前に条件がロックを取得する必要があるため、そのキューの動作条件は、スレッドセーフであります

await()メソッド

await()を達成するには、3つの異なる方法があります。

  1. awaitUninterruptibly :Doが割り込み条件が目覚めたことが満たされるまで待たなければならない、応答期間を待っていません

  2. await() throws InterruptedException :あまりにも長い間遮断された場合に割り込み応答時間のawait、いつでもウェイクで中断することができます

  3. await(long time, TimeUnit unit) throws InterruptedException :あなたは、タイムアウト待ちを設定することができ、および割り込みに応答することができます

使用して割り込みを処理するためinterruptMode 1の値のための最終的な定数を中断し、現在のスレッド以降の必要性が、-1手段後続必要が例外:InterruptedExceptionをスローすることを示しています。要するに、この定数は、将来的には、この混乱に対処する方法をマークするために使用されます。プライベート静的最終int型REINTERRUPT = 1;プライベート静的最終int型のTHROW_IE = -1; 私たちの具体的な分析の第三の実施例:

 public final boolean await(long time, TimeUnit unit)
                throws InterruptedException {
            long nanosTimeout = unit.toNanos(time);
// 因为本身await期间要响应中断,在await前先判断是否已被中断了,已中断就抛InterruptedException
            if (Thread.interrupted())
                throw new InterruptedException();
				//将当前线程封装成Node节点并加入等待队列的尾部
            Node node = addConditionWaiter();
						// 释放当前线程所占有的锁,如果是可重入锁,也要把state值归为0
            int savedState = fullyRelease(node);
            final long deadline = System.nanoTime() + nanosTimeout;
            boolean timedout = false;
            int interruptMode = 0;
// while只要不退出,就说明还在等待队列中进行await
            while (!isOnSyncQueue(node)) {
// 如果到了超时时间,会将节点从等待队列转移到同步队列,返回true,说明等待真的超时。返回false,说明当正准备取消等待前,已经被signal了,只是还没有完成转移到同步队列而已
                if (nanosTimeout <= 0L) {
                    timedout = transferAfterCancelledWait(node);
                    break;
                }
//如果等待剩余时间少于1000纳秒就没必要park了,不如自旋,毕竟很快就要退出while循环了
                if (nanosTimeout >= spinForTimeoutThreshold)
                    LockSupport.parkNanos(this, nanosTimeout);
// != 0意味着 waiting期间被中断,因为要响应中断,所以break,没必要再await; 等于0,意味着waiting期间没有被中断
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
                nanosTimeout = deadline - System.nanoTime();
            }
// 退出上面while后,说明已经在同步队列了,此线程开始抢锁(试图恢复await前的state值),如果acquireQueued 返回false,说明在同步队列里获取锁的过程中没有被中断过,返回true则表示曾发生过中断
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null)
                unlinkCancelledWaiters();
// 统一处理上述过程产生的中断状态
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
            return !timedout;
        }
复制代码

なぜ奇妙なことは、この一定のスレッドが対処する方法の未来をマークするために使用されているものですか?むしろ、そのような即時の治療は、この方法を多重化するための個人的な理解、よりacquireQueued外部インタフェースおよび割り込みに対応するためにいくつかの必要性など、他の人は、手段は中断に対処するためのさまざまな方法を持っている、ないacquireQueued割り込みが発生した場合にのみなしで、返します実際の内部割り込み処理を行うことで、上位層への実際の処理。

我々の詳細の下に解体await()重要な方法のいくつか。

isOnSyncQueue()

キューノードの状態は同期キューに転送されたかどうかを決定するためのこの方法。

 final boolean isOnSyncQueue(Node node) {
 // 如果节点的waitStatus 依然为Node.CONDITION,说明还在条件队列,否则如果已被转移到同步队列中时waitStatus应为0或-1
 // node.prev 是在同步队列才会用的属性,==null 依然意味着没有进入同步队列
        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为非空,依然不能确定其已在同步队列中,因为同步队列的节点入队是两步操作,先设置node.prev,然后CAS设置自己为tail,第二步操作可能CAS失败。
 //从同步队列尾节点往前找
        return findNodeFromTail(node);
    }
复制代码

信号()

同期キューのこの方法の条件キューヘッドノード

 public final void signal() {
 // 调用signal的线程必须持有独占锁
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
				
				
复制代码
private void doSignal(Node first) {
            do {
	// 因为first马上就要被转移到同步队列了,所以将first.nextWaiter,作为新的firstWatier。
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
					//切断和等待队列的关联
                first.nextWaiter = null;
								// 如果转移不成功且还有后续节点,那么继续后续节点的转移
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
        }
复制代码
 final boolean transferForSignal(Node node) {
 // 进行CAS,毕竟因为当前发起signal的是另一个线程,而node本身可能自己取消等待,所以需要CAS
//如果CAS失败 说明此节点已取消等待,此节点接下来将不会被转移到同步队列, 如果CAS成功,waitStatus将会被置为0
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;

// 将node 加入同步队列后返回其前置节点
        Node p = enq(node);
        int ws = p.waitStatus;
// ws > 0 说明 node 在阻塞队列中的前驱节点取消了等待锁,直接唤醒 node 对应的线程。ws < 0时CAS设置node前置节点的waitStatus为SIGNAL,之前文章说过,新节点入同步队列需要设置前置节点waitStatus为SIGNAL,肩负起唤醒后继节点的责任

// 所以如果 node进入同步队列后的前置节点取消或者 CAS设置SIGNAL失败,直接唤醒该node
// 但是在绝大多数情况下 应该是ws<0,并且CAS成功的,并不会直接unpark,而是等到在同步队列中成功拿到锁后被unpark
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
	
复制代码

参照await()方法でコードを、unparkをすると、次の実行に進みます。3例でしょうLockSupport.parkは(この)ありますが、この文をダウン継続に戻ります。

  1. 従来型パス。信号 - >転送ノードにブロックされたキュー - >(unparkをする)ロックを取得します。
  2. スレッドの割り込み。このスレッドに別のスレッドが中断された公園では、
  3. 信号我々はそれを言ったとき、前駆体ノードの転送後に除去、またはCASノード演算の前駆体が失敗
  4. 偽のウェイク。これはまた存在し、はObject.wait()は同様に、問題があります

ノードがしなければならないから、ここに来てpark返され、ゼロでない場合は、中断を示す、割り込み状態をチェックした後の戻りが発生しました。0、中断されていません

 if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
复制代码
private int checkInterruptWhileWaiting(Node node) {
            return Thread.interrupted() ?
						// transferAfterCancelledWait 方法会判断被unpark的节点曾被中断的时机,如果返回true,意味着在条件队列中等待的时候被中断过(未被signal之前),false意味着中断发生在被signal之后
                (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
                0;
        }
复制代码

中断が発生した場合にのみ、我々はこの呼ぶtransferAfterCancelledWait方法を。そのようなAシナリオの出現として理解することができる:条件キューノードはまた静かに同期キューに移行した後の状態を待っているの公園があり、それが他のスレッドによって中断されました

final boolean transferAfterCancelledWait(Node node) {
// 此处CAS设置成功意味着node还在条件队列中
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
				// 所以在条件队列中即在signal之前被中断,那么将node加入到同步队列,并且返回true
            enq(node);
            return true;
        }
        /*
         * If we lost out to a signal(), then we can't proceed
         * until it finishes its enq().  Cancelling during an
         * incomplete transfer is both rare and transient, so just
         * spin.
         */
// 走到这里大概率是已经在同步队列了,也可能是正在加入同步队列的过程中,自旋等待入队完成。总之中断是发生在已经被signal之后了。
        while (!isOnSyncQueue(node))
            Thread.yield();
        return false;
    }
复制代码

割り込み状態をチェックする過程で、我々はわかります、されunpark、後かどうかに関係なく、中断のにかかわらず、割り込み応答するかどうかのawait方法は、キューの同期に追加され、割り込みの処理に関する特別ですので何をしていないようでした。それは少しではなく、我々の期待と一致していませんか?

だから、終了条件付きループは2があります。awaitwhile ()

  1. あなたはいくつかの条件は、同期キューで発見されていると述べた前に一つのノードを目覚めさせた後
  2. いくつかの条件が中断によって引き起こされることが判明する前に、ノードは、ウェイクアップコールの一つである後unpark、直接ブレーク;アウトwhileループの

私たちは、見続けるawait()の活路をwhileサイクル

// 执行到这里,该node一定是已经进入同步队列了
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
// 能进入if ,一定是在被signal之后发生的中断,标志下接下来的处理中,需要重新进行中断
                interruptMode = REINTERRUPT;
            if (node.nextWaiter != null) // clean up if cancelled
                unlinkCancelledWaiters();
            if (interruptMode != 0)
                reportInterruptAfterWait(interruptMode);
复制代码

acquireQueued戻り値は中断され、同期キュー・プロセスでロック取得示してtrueで、上記while電流によって中断されていない場合、終了前にinterruptModeあるため、フラグ変数、メソッド呼び出し、割り込みステータスフラグがクリアされています。上記の場合はセットアップ、ことを示したが、ステージを発生し、中断または中断されていないが、条件キュー上のノードで中断された後に起こっ。checkInterruptWhileWaitingThread.interrupted()interruptMode != THROW_IEwhilesignal

要約するとそれらが出るほど言うするwhileループを、か中断され、その後、ノードは、同期キューでなければならず、実行時acquireQueuedロックとリターンを得るための手段を、戻り値がロックを取得するために、同期キューのプロセスを表しているかどうかを中断さ、私は上部の統一「という事、後に」それからの割り込みスレッドに、見ることができるとキューとの同期ロックに入るためにその成功に影響しませんが、全体のプロセスは、最初に記録中断されたかどうか、およびので、処理-使用reportInterruptAfterWait()方法。あなたが割り込み応答を見ることができた場合はawait、信号が中断される前に、キュー時の条件ならば、そしてinterruptMode == THROW_IE、例外がスローされます。

 private void reportInterruptAfterWait(int interruptMode)
            throws InterruptedException {
            if (interruptMode == THROW_IE)
                throw new InterruptedException();
            else if (interruptMode == REINTERRUPT)
                selfInterrupt();
        }
复制代码

概要

JUCの実装は確かに見つけ、新たな洞察があるだろうたびを読んで、多くのデザインのヒントがあるいくつかの以前に未知の、非常に多くの場合、彼らは時間の他のすべての期間を見ていき、理解していない「賢いけど役に立たない」I私は、息の大学を覚えている「Javaの並行プログラミングの技術では、」より多くのより深い理解を支払うためにそれを介して二、三回、それぞれの読み取りを読んでご覧ください。この研究の背面にJUCが「今よりゼロから移動し、鉄などの男道」を考えることができる〜、今は非常にリラックス見直しの基礎,,,

おすすめ

転載: juejin.im/post/5df61d21518825124d6c18fd