[Java同時プログラミング実戦]14章カスタム同期ツールの構築

21267 ワード

ステータス依存性の管理

  • 前提条件を構成する状態変数は、オブジェクトのロックによって保護され、前提条件をテストしながら一定に保たなければならない.前提条件が満たされていない場合は、他のスレッドがオブジェクトの状態を変更できるようにロックを解除する必要があります.そうしないと、前提条件は永遠に真になりません.前提条件を再テストする前に、ロック
  • を再取得する必要があります.
  • 前提条件の失敗を呼び出し者に渡す、呼び出し者はスリープ待ち、スピン待ち、またはThreadを呼び出すことができる.yield
  • は、単純な「ポーリングとスリープ」再試行メカニズムによってブロックを実現するとともに、前提条件の管理操作を
  • にカプセル化することができる.

    じょうけんキュー

  • は、スレッドのセット(待機スレッドのセット)が特定の条件が真になるのを何らかの方法で待つことができるようにする.条件キューの要素は、関連条件を待機しているスレッド
  • です.
  • Javaオブジェクトごとにロックとしてもよいし、条件キューとしてもよいし、Objectのwait/notify/notifyAllメソッドが内部条件キューのAPIを構成している.オブジェクトの組み込みロックは、その内部条件キューと相互に関連するものであり、オブジェクトXの条件キューのいずれかのメソッドを呼び出すには、オブジェクトXのロック
  • を持つ必要がある.
  • Object.waitは自動的にロックを解放し、オペレーティングシステムに現在のスレッドの停止を要求し、他のスレッドがこのロックを取得してオブジェクトの状態を変更できるようにします.保留中のスレッドが目を覚ますと、彼は戻る前にロック
  • を再取得する.
        public synchronized void put(V v) throws InterruptedException{
            while(isFull()){
                System.out.println(new Date()+" buffer  , thread wait:"+Thread.currentThread().getName());
                wait();
            }
            doPut(v);
            System.out.println(new Date()+" "+Thread.currentThread().getName()+"   :"+v+" ");
            notifyAll();
        }
        
        public synchronized V take() throws InterruptedException {
            while(isEmpty()){
                System.out.println(new Date()+" buffer  , thread wait:"+Thread.currentThread().getName());
                wait();
            }
                    
            notifyAll();        
            // , ,  
            V v = doTake();        
            System.out.println(new Date()+" "+Thread.currentThread().getName()+"   :"+v);
            return v;
        }
    
  • 条件述語:ある操作を状態依存操作にする前提条件である.スレッドがwaitから起動するたびに、条件述語を再テストする必要があるため、wait
  • を1サイクルで呼び出す必要がある.
    void stateDependentMethod() throws InterruptedException{
        synchronized(lock){
            while(!conditionPredicate())
                lock.wait();
            // 
        }
    }
    
  • 使用条件待ち時(Object.wait/Clondition.await)
  • には、通常、いくつかのオブジェクト状態のテストを含む条件述語があり、スレッドは、実行前にこれらのテスト
  • を最初に通過しなければならない.
  • waitを呼び出す前に条件述語をテストし、waitから戻ると再びテスト
  • を行う.
  • wait
  • を1サイクルで呼び出す
  • は、条件述語を構成する各状態変数
  • を保護するために、条件キューに関連するロックが使用されることを保証する.
  • wait/notify/notifyAllなどのメソッドを呼び出す場合は、必ず条件キューに関連するロック
  • を持つ.
  • 条件述語をチェックした後、および対応する動作を開始する前に、ロック
  • を解放しないでください.
  • がnotifyを呼び出すと、JVMはこの条件キューで待機している複数のスレッドの中から1つを選択して起動し(単一の通知では信号が失われやすい)、notifyAllを呼び出すと、この条件キューで待機しているすべてのスレッド
  • が起動する.
  • は、以下の2つの条件を同時に満たす場合にのみ、単一のnotifyを使用することができる.
  • すべての待機スレッドのタイプは同じです.条件述語が条件キューに関連付けられているのは1つだけで、各スレッドはwaitから戻ると同じ操作
  • を実行します.
  • 単進単出:条件変数の通知ごとに、最大
  • を実行するために1つのスレッドしか起動できない.
  • は、waitおよびnotifyメソッドの正しい使用を「入口プロトコル」および「出口プロトコル」で記述する.
  • は、各依存状態の動作、および他の動作依存状態を変更する各動作について、入口プロトコルおよび出口プロトコル
  • を定義すべきである.
  • エントリプロトコル:この動作の条件述語
  • である.
  • 出口プロトコル:この操作によって変更されたすべての状態変数を検査し、他の条件述語が真になるかどうかを確認することを含む.もしそうであれば、関連する条件キュー
  • に通知する.

    明示的なConditionオブジェクト

  • Lockは一般化された内蔵ロックであり、Conditionも一般化された内蔵条件キュー
  • である.
  • は、内蔵条件キューとは異なり、ロックごとに任意の数のConditionオブジェクトを持つことができる.Conditionは、組み込み条件キューよりも豊富な機能を提供する:各ロックに複数の待機が存在し得る、条件待機が中断可能または中断不可であり得る、時間ベースの待機、および公平または非公平なキュー操作
  • が存在する.
  • 明示的なLockとConditionを使用する場合は、ロック、条件述語、条件変数の3つの関係を満たす必要があります.条件述語に含まれる変数はロックによって保護され、条件述語のチェックおよびawaitとsignalの呼び出し時には、Lockオブジェクト
  • を持つ必要があります.
    /**
     *  
     */
    public class ConditionBoundedBuffer<T> {
        protected final Lock lock = new ReentrantLock();
        // :notFull (count < items.length)
        private final Condition notFull = lock.newCondition();
        // :notEmpty (count > 0)
        private final Condition notEmpty = lock.newCondition();
        private static final int BUFFER_SIZE = 100;
        @GuardedBy("lock")
        private final T[] items = (T[]) new Object[BUFFER_SIZE];
        @GuardedBy("lock")
        private int tail,head,count;
        
        // :notFull
        public void put(T x) throws InterruptedException {
            lock.lock();
            try{
                while(count == items.length)
                    notFull.await();
                items[tail] = x;
                if(++tail == items.length)
                    tail = 0;
                ++count;
                notEmpty.signal();
            }finally{
                lock.unlock();
            }
        }
        
        // :notEmpty
        public T take() throws InterruptedException{
            lock.lock();
            try{
                while(count==0)
                    notEmpty.await();
                T x = items[head];
                items[head] = null;
                if(++head == items.length)
                    head = 0;
                --count;
                notEmpty.signal();
                return x;
            }finally{
                lock.unlock();
            }
        }
    
    }
    

    AbstractQueuedSynchronizer

  • LOCKの実装クラスは、AbstractQueuedSynchronizer上に構築されており、各ロック実装クラスは独自の内部クラスSyncのインスタンスを持っているが、このSyncはAbstractQueuedSynchronizer(AQS)を継承している.
  • はvolatile変数stateを提供する.スレッド間の共有ステータスを同期します.CASとvolatileによりその原子性と可視性を保証した.
  • /** 
     *   
     */  
    private volatile int state;  
      
    /** 
     *cas 
     */  
    protected final boolean compareAndSetState(int expect, int update) {  
        // See below for intrinsics setup to support this  
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);  
    }
    

    同期器はロックを実現する鍵であり、同期器を利用してロックの意味を実現し、ロックの実現で同期器を集約する.ロックのAPIは使用者向けであり、ロックと相互作用する共通の動作を定義しているが、各ロックが特定の動作を完了する必要があるのもこれらの動作を通じて行われる(例えば、2つのスレッドをロックし、2つ以上のスレッドを排除することができる)が、実装は同期器に依存して行われる.同期器はスレッドアクセスとリソース制御に向いており、スレッドがリソースを取得できるかどうかやスレッドのキューなどの操作を定義しています.ロックと同期器は両者が注目すべき分野をよく隔離しており、厳密には、同期器はロック以外の同期施設(括弧ロック)に適用できる.

    java.util.concurrentシンクロナイザクラスのAQS


    java.util.concurrentの多くのブロッキング可能クラスはAQSに基づいて構築されています
  • ReentrantLock:
  • は独占方式の取得操作のみをサポートし、tryAcquire/tryRelease/isHeldExclusively
  • を実現した.
  • は、同期状態をロック取得動作を保存する回数に使用し、owner変数を維持して現在の所有者スレッドの識別子
  • を保存する.
  • Semaphore:
  • AQSの同期状態を現在利用可能なライセンス数
  • を保存するために使用する.
  • CountDownLatch:
  • 同期状態に現在のカウント値
  • が保存する.
  • FutureTask:
  • AQS同期状態タスクを保存するための状態:実行中、完了、キャンセル
  • はまた、いくつかの追加の状態変数を維持し、計算結果を保存するか、異常
  • を放出する.
  • はまた、計算タスクを実行するスレッドを指す参照を維持するので、タスクはキャンセルされ、スレッドは
  • 中断される.
  • ReentrantReadWriteLock:
  • 内部の1つのAQSサブクラスは、読み出しロックと書き込みロック
  • を同時に管理する.
  • は、一方の16ビットの状態を用いる書き込みロックのカウントを表し、他方の16ビットの状態を用いる読み出しロックのカウント
  • を表す.
  • 読み出しロックの動作は、共有の取得及び解放方法を用いる、書き込みロックは、排他的な取得解放方法
  • を用いる.
  • AQSは内部で待機スレッドキューを維持し、あるスレッドが独占アクセスであるか共有アクセスであるかを記録する
  • .
  • ロックが使用可能である場合、キューヘッダに位置するスレッドが書き込み操作を実行すると、スレッドはこのロックを得る.このロック
  • は、キューヘッダに位置するスレッドがリードアクセスを実行すると、キュー内の最初のスレッドが書き込まれる前にすべてのスレッドが取得する.