同時-CountDownLatchの実現原理を深く分析する

8768 ワード

一、前言
  最近java.util.concurrentパッケージのいくつかの常用クラスを研究して、前にAQSReentrantLockArrayBlockingQueueおよびLinkedBlockingQueueの関連ブログを書いて、今日このブログは書いて発注したもう一つの常用クラス--CountDownLatchを書きます.ここでまず、CountDownLatchAQSに基づいて実現され、AQSこそスレッド同期を実現したコンポーネントであり、CountDownLatchはその使用者にすぎないので、CountDownLatchを学ぶには、AQSの実現原理を理解しておく必要があります.私の以下の説明は、AQSを理解した上で確立されています.私は前にAQS実現原理の分析ブログを書いたことがあります.興味があれば、同時--抽象キュー同期器AQSの実現原理を見てみましょう.
二、本文
2.1抽象キュー同期器AQSCountDownLatchを言う前に、AQSを先に言わなければなりません.AQS全称抽象キュー同期器(AbstractQuenedSynchronizer)は、スレッド同期を実現するための基礎フレームワークである.もちろん、それは私たちが理解しているSpringというフレームワークではありません.それはクラスです.クラス名はAbstractQuenedSynchronizerです.スレッドの同期を完了できるロックや類似の同期コンポーネントを実現したい場合は、AQSを使用して実現することができます.それはスレッドの同じステップをカプセル化しているので、私たちは自分のクラスで使用しています.私たち自身のロックを簡単に実現することができます.
  AQSの実現は比較的複雑で、短いいくつかの言葉ではっきり言えない.私は以前、AQSの実現原理を分析するブログを書いたことがある.同時--抽象キュー同期器AQSの実現原理.
  次の内容を読む前に、必ずAQSの実現原理を勉強してください.CountDownLatchの実現は非常に簡単で、完全にAQSに依存しているので、私の以下の説明はすべて理解したAQSの基礎の上に構築されています.上の推薦ブログを読むこともできますし、自分で関連資料を調べることもできます.
2.2 CountDownLatchの実現原理
すでにCountDownLatchの実現原理を学び始めた以上、その役割はきっとすでに知っているに違いない.私はここで詳しく展示しない.簡単に紹介する:CountDownLatchのドア栓と呼ばれ、ドアの鍵と見なすことができ、ドアに多くの鍵を与え、すべての鍵が解けてこそ、通過することができる.スレッドの場合、CountDownLatchはスレッドの実行をブロックし、CountDownLatcの内部レコードの値が0に減少した場合にのみ、スレッドは前進し続けることができます.
  CountDownLatch下層はAQSで実現され、AQSの一般的な使用方法は内部クラスの形で継承され、CountDownLatchはこのように使用される.CountDownLatchの内部にはSyncから継承された内部クラスAQSがあり、AQSのロック解除方法を再書き込み、Syncのオブジェクトを介してAQSの方法を呼び出し、スレッドの実行をブロックする.CountDownLatchオブジェクトを作成する場合、countメソッドを通過するには、count0に減少した場合にのみ、awaitメソッドを通過する必要があります.そうしないと、awaitにブロックされます.ここでは実際には、スレッドがawaitメソッドに実行されると、ロックを取得する必要があり(ロックはAQSによって実現される)、countが0でないと、スレッドはロックを取得できず、ブロックされる.countが0であれば、スムーズに通過できます.CountDownLatchは使い捨てであり、countの値を増やす方法がないため、すなわち、count0に減少すると、その後も0になり、スレッドをブロックすることはできなくなる.次に、ソースコードの観点からCountDownLatchを分析します.
2.3 CountDownLatchの内部クラス
前述したように、CountDownLatch内部にはSyncという内部クラスが定義されており、AQSから継承されており、この内部クラスによってスレッドブロックが実現されています.次に、この内部クラスの実装を見てみましょう.
private static final class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 4982264981922014374L;

    /**     ,  count ,  count   0 ,      await     */
    Sync(int count) {
        // CountDownLatch  AQS        count  AQS     state
        //      state  count 
        setState(count);
    }

    /**      count  */
    int getCount() {
        return getState();
    }

    /** 
     *   AQS     acquireShared、acquireSharedInterruptibly            ,
     *      ,                 ,  AQS  ,
     *           0,         ,          ,
     *     ,       count    0,        
     */
    protected int tryAcquireShared(int acquires) {
        //     state     0,     count   0,
        //  count 0,  1,       ,         ,    
        //  count  0,   -1,       ,       
        //            CountDownLatch      
        return (getState() == 0) ? 1 : -1;
    }

    /**
     *            AQS    ,  true      ,     
     *       AQS     releaseShared    ,
     *  CountDownLatch ,        count 
     */
    protected boolean tryReleaseShared(int releases) {
        //             
        for (;;) {
            //       state  ,   count 
            int c = getState();
            //  count     0,        ,      false
            //        false,    0                 ,
            //    true,AQS       ,   false,     ,  
            //            ,  false        
            if (c == 0)
                return false;
            //  count   0,   -1
            int nextc = c-1;
            // compareAndSetState     count  c,     nextc
            //      CAS  ,         
            if (compareAndSetState(c, nextc))
                //  nextc == 0,     true,        ,       ,
                //  nextc > 0,            ,     false
                return nextc == 0;
        }
    }
}

内部クラスSyncの実装は非常に簡単であり、AQSで提供される共有ロックを使用するインタフェースであるtryAcquireSharedとtryReleaseSharedの2つの方法しか実装されていないことがわかります.これは、AQSが実際には共有ロックメカニズムであることを示している.すなわち、ロックが同時に複数のスレッドによって取得されることができる.これは、CountDownLatchが0に減少すると、すべてのスレッドがcountメソッドを通過すると、ロックが取得されないためにブロックされないため、スムーズに通過することができるからである.また、上記の実装から、awaitは、Syncの値をcountAQSの値として直接使用し、stateの値が0である限り、スレッドはロック、すなわち実行権限を取得することができる.
2.4 CountDownLatchのメンバー変数と構築方法
次に、stateの属性と構造方法を見てみましょう.
/**
 *         ,     Sync     ,       AQS   ,         
 */
private final Sync sync;


/**
 *         ,    count 
 */
public CountDownLatch(int count) {
    // count     0
    if (count < 0) throw new IllegalArgumentException("count < 0");
    //       Sync  ,   count ,Sync      setState(count)
    this.sync = new Sync(count);
}

2.5 awaitメソッド分析
  CountDownLatch類の最も核心的な2つの方法はCountDownLatchawaitであり、まずountDown方法の実現を見てみましょう.
//             ,  count   0     
public void await() throws InterruptedException {
    //       sync acquireSharedInterruptibly  ,       AQS 
    //              ,     ,         AQS        
    //         。            ,         ,        ,
    //                   。
    sync.acquireSharedInterruptibly(1);
}

  awaitの実現は異常に簡単で、わずか1行のコードしかなく、awaitでカプセル化された方法を呼び出した.これがAQSの利点であり、AQSはスレッドのブロックと起動メカニズムを実現し、実現の複雑さを隠すが、他のクラスは簡単に使用するだけでよい.わかりやすいように、AQS方法を見てみましょう.
/**     AQS          ,       ,        */
public final void acquireSharedInterruptibly(int arg)
    throws InterruptedException {
    //              ,    ,         
    if (Thread.interrupted())
        throw new InterruptedException();
    
    //   tryAcquireShared       ,     Sycn    ,
    //  count == 0,        1,       ,        ,       
    //  count < 0,       doAcquireSharedInterruptibly  ,
    //       Sync tryAcquireShared     
    if (tryAcquireShared(arg) < 0)
        //           ,       ,     AQS          ,
        //         ,              count == 0,            ,
        //        ,                ,      ,
        //      ,  count == 0 ,               
        doAcquireSharedInterruptibly(arg);
}

  ここを見て、acquireSharedInterruptiblyの実現原理について比較的明確な理解が得られたと信じています.CountDownLatchの実現は完全にCountDownLatchに依存しており、以上の内容が理解できない場合は、先にAQSを勉強してください.
2.6 countDownメソッド分析
次に、AQSのもう一つのコアの方法を分析します.CountDownLatch
/**
 *          count  -1,  count  0 ,        
 */
public void countDown() {
    //       sync releaseShared  ,        AQS ,  AQS       ,
    //                ,     ,  false,     ,   false,
    //        ,        AQS              ,       
    //   CountDownLatch  ,         count - 1,   count    0,
    //         ,          
    sync.releaseShared(1);
}

  理解を容易にするために、countDownAQS方法の実現を見てみましょう.
public final boolean releaseShared(int arg) {
    //   tryReleaseShared     ,       Sycn  ,            
    //  tryReleaseShared  true,  count       ,  0 ,    doReleaseShared
    if (tryReleaseShared(arg)) {
        //           AQS      ,          
        //      acquireSharedInterruptibly       ,
        //         ,   count == 0,             
        //     ,        , count == 0 ,         
        doReleaseShared();
        return true;
    }
    return false;
}

三、まとめ
  releaseSharedのソースコードを直接見てみると、その実現は本当に簡単で、注釈を含めて、合計CountDownLatch行コードで、注釈を除いて、300行コードさえありません.これは、100を書き換える2つの方法を除いて、基本的にはAQSが提供するテンプレートメソッドを呼び出すことである.したがって、AQSを理解する過程は、実際にはCountDownLatchを理解する過程であり、AQSを理解し、AQSの原理を理解すれば、CountDownLatch分を必要としない.5は本当にAQSの合併の中で非常に重要なコンポーネントであり、多くの種類はそれに基づいて実現されている.例えば、Javaがあり、同時にReentrantLockも面接の常考点であるため、よく研究しなければならない.最後に、私が前に書いたAQSに関するソース分析ブログ:同時-抽象キュー同期器AQSの実現原理を再びお勧めします.
四、参考
  • JDK1.8ソース