JUCの遊び

6930 ワード

毎号1つの小さい知識点と関連する面接問題を総括して、へへ、またみんなと一緒に勉強しに来ました.
GUCには私たちが使っているクラスが少ないが、彼は確かに多くのクラスに欠かせないメンバーだ.彼はConditionです.
文字通り条件ですが、その条件ならtrue or falseです.そのConditionはマルチスレッド共有識別ビットがブロックを実行する役割を果たし,trueのときに通過し,falseのときに待機する.
1、Conditionの使用
次のコードから、どのように使用するかがわかります.
// thread 1
System.out.println("1 am thread 1 start");
condition.await();//  
System.out.println("1 am thread 1 end");

// thread 2
System.out.println("1 am thread 2");
condition.signal()://  

スレッド1とスレッド2を同時に実行するとします.実行後の出力は次のようになります.
1 am thread 1 start
1 am thread 2
1 am thread 1 end

ありません.Objectオブジェクトのwait()、notify()に似ているかどうかがわかります.唯一の違いは、Conditionがブロックメソッドを呼び出すにはsynchronize修飾を必要としないことです.それはもっと使いやすくなったのではないでしょうか.ブロックキュー内のemptyやfullのような判断はConditionに基づいて実現され,通知順序を保証できる.
2、Conditionの原理
1つのConditionインスタンスは本質的にロックにバインドされます.特定のConditionインスタンスのConditionインスタンスを取得するには、そのnewCondition()メソッドを使用します.
   final Lock lock = new ReentrantLock();
   //      lock
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

2.1 Condition API
Modifier and Type
Method and Description void await()は、現在のスレッドが信号を送信するまで待つか、またはinterruptedをもたらす.boolean await(long time, TimeUnit unit)は、現在のスレッドを、信号の送信または中断、または指定された待機時間が経過するまで待機させる.long awaitNanos(long nanosTimeout)は、現在のスレッドを、信号の送信または中断、または指定された待機時間が経過するまで待機させる.void awaitUninterruptibly()は、信号が送信されるまで現在のスレッドを待機させる.boolean awaitUntil(Date deadline)は、信号の送信または中断、または指定された最終期限が経過するまで、現在のスレッドを待機させる.void signal()は、待機スレッドを起動します.void signalAll()は、すべての待機スレッドを起動します.
2.1 Condition実装
初期化方法:
final ConditionObject newCondition() {
  // ConditionObject AQS    ,                    
  return new ConditionObject();
}

まずawaitメソッドを見て、ブロック待ちをどのように実現するかを見ます.
public final void await() throws InterruptedException {
    //          ,    InterruptedException
    if (Thread.interrupted())
        throw new InterruptedException();
    //       node,     Condition   AQS node       
    Node node = addConditionWaiter();
    //  node         ;      
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //       ?isOnSyncQueue Node next      true,          LCH          。
    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();
    // 0     
    if (interruptMode != 0)
        // interrupt  
        reportInterruptAfterWait(interruptMode);
}

private Node addConditionWaiter() {
    Node t = lastWaiter;
    // If lastWaiter is cancelled, clean out.
    if (t != null && t.waitStatus != Node.CONDITION) {
        unlinkCancelledWaiters();
        t = lastWaiter;
    }
    //     condition   Node
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

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

では、signalがどのようにモーニングコールを実現するかを見てみましょう.
public final void signal() {
    //            ,lock        ,getExclusiveOwnerThread() == Thread.currentThread();
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    //      node     
    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);
}


final boolean transferForSignal(Node node) {
    /*
     * If cannot change waitStatus, the node has been cancelled.
     */
    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).
     */
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        // unpark    
        LockSupport.unpark(node.thread);
    return true;
}

3、Condition関連面接問題
3.1、Javaの虚偽の目覚ましとどのように虚偽の目覚ましを避けるのか.
偽りが呼び覚ます
1つの条件が満たされると、多くのスレッドが起動されますが、その一部だけが有用な起動であり、他の起動は無駄です.
例えば买い物をして、もし商品がもともと品物がないならば、突然1件の商品に入って、これはすべてのスレッドがすべて呼び覚まされたので、しかし一人で买うことしかできなくて、だから他の人はすべて伪りの呼び覚ますので、対象の键を得ることができません
虚偽の目覚めを避けるには
すべてのスレッドが起動された場合,臨界条件を判断するにはwhileを用いて判断し,起動された場合にもう一度条件をチェックすることができる.
3.2、Mutex、BooleanLatchどんなシーンで使うか
Mutex:これは、ロック解除状態、ロック状態を表すゼロ値を使用する反発ロッククラスです.再ロック不可は、現在の所有者スレッドの記録を厳格に要求しないが、これにより、使用が監視されやすくなる.また、条件をサポートし、その中の1つの機器方法を公開します.
BooleanLatch:CountDownLatchのようなラッチクラスですが、トリガするにはsignalが1つしか必要ありません.
3.3、CLHロックとMCSロックの違い
  • コード実装から見ると、CLHはMCSよりずっと簡単である.
  • スピンの条件から見ると、CLHは前駆ノードの属性上でスピンし、MCSはローカル属性変数上でスピンする.
  • チェーンテーブルキューから見ると、CLHNodeは直接前駆ノードを持たず、CLHロックが解放されるときは自分の属性を変えるだけである.MCSNodeは直接後続ノードを持ち,MCSロック解放には後続ノードの属性を変更する必要がある.
  • CLHロック解放時は自分の属性を変更するだけで、MCSロック解放は後続ノードの属性
  • を変更する必要がある.
    3.4、ノードの状態はどれらがありますか
  • CANCELELED(1):現在のノードがスケジュール解除されていることを示します.timeoutまたは割り込まれた場合(応答割り込みの場合)、この状態に変更がトリガーされ、その状態に入った後のノードは変化しません.
  • SIGNAL(-1):後続ノードが現在のノードの起動を待っていることを示します.後継ノードがエンキューされると、前継ノードの状態がSIGNALに更新されます.
  • CONDITION(-2):ノードがConditionに待機していることを示し、他のスレッドがConditionのsignal()メソッドを呼び出すと、CONDITION状態のノードが待機キューから同期キューに移行し、同期ロックの取得を待つ.
  • PROPAGATE(-3):共有モードでは、前継ノードが後続ノードを呼び覚ますだけでなく、後続ノードを呼び覚ます可能性もあります.
  • 0:新しいノードがエンキューされたときのデフォルトの状態.

  • 本文は猿必過
    YBGリリース