モジュール java.base

クラスAbstractQueuedSynchronizer

java.lang.Object
java.util.concurrent.locks.AbstractOwnableSynchronizer
java.util.concurrent.locks.AbstractQueuedSynchronizer
すべての実装されたインタフェース:
Serializable

public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements Serializable
ブロック・ロック、および先入れ先出し(FIFO)待機キューに依存する関連シンクロナイザ(セマフォ、イベントなど)を実装するフレームワークを提供します。 このクラスは、状態表現を単一の原子int値に依存する大半の種類のシンクロナイザの有用な基盤として設計されています。 サブクラスは、この状態を変更するprotectedメソッドを定義する必要があり、そのメソッドは取得または解放中のオブジェクトを使用して状態の意味を定義します。 これらが指定されると、このクラス内のほかのメソッドはすべてのキューおよびブロック機構を稼働させます。 サブクラスは、ほかの状態フィールドを維持できますが、同期に関する追跡を行えるのはgetState()setState(int)、およびcompareAndSetState(int, int)メソッドを使用して操作され、原子的に更新されたint値だけです。

サブクラスは、publicではない内部ヘルパー・クラスとして定義する必要があります。これは、それを囲むクラスの同期プロパティの実装に使用されます。 AbstractQueuedSynchronizerクラスは、同期インタフェースを一切実装しません。 かわりに、具象ロックおよび関連するシンクロナイザにより適宜呼び出してpublicメソッドを実装できるacquireInterruptibly(int)などのメソッドを定義します。

このクラスは、デフォルトの排他モードと共有モードのどちらかまたは両方をサポートします。 排他モードで取得されると、ほかのスレッドが取得を試みても成功しません。 共有モードでは、複数のスレッドによる取得が可能です(ただし、必ず取得が成功する必要があるわけではない)。 このクラスは、共有モードの取得が成功した場合、待機中の次のスレッド(存在する場合)も取得可能かどうかを判別する必要があるという機構的な意味を除き、これらの違いを「認識」しません。 別のモードで待機中のスレッドは、同じFIFOキューを共有します。 通常、実装サブクラスはこれらのモードの1つだけをサポートしますが、ReadWriteLockなどでは両方が機能することが可能です。 排他モードまたは共有モードのみをサポートするサブクラスは、使用しないモードをサポートするメソッドを定義する必要はありません。

このクラスは、Conditionの実装として使用できるネストされたAbstractQueuedSynchronizer.ConditionObjectクラスを定義します。このクラスは、同期が現在のスレッドに関して排他的に保持されているかどうかをメソッドisHeldExclusively()が報告し、現在のgetState()値とともに呼び出されるメソッドrelease(int)がこのオブジェクトを完全に解放し、acquire(java.util.concurrent.locks.AbstractQueuedSynchronizer.Node, int, boolean, boolean, boolean, long)(この保存済状態の値を指定)したメソッドを最終的に、このオブジェクトを以前に取得した状態にリストアします。 それ以外では、どのAbstractQueuedSynchronizerメソッドもこのような状態を作成しないため、この制約を満たすことができない場合は使用しないでください。 AbstractQueuedSynchronizer.ConditionObjectの動作はもちろん、そのシンクロナイザ実装のセマンティックスに依存します。

このクラスは、内部キューの検証、計測、および監視用メソッド、および状態オブジェクト用の類似メソッドを提供します。 これらは、必要に応じて、その同期機構のためのAbstractQueuedSynchronizerを使用してクラス内にエクスポートできます。

このクラスを直列化すると、基になる原子整数の保守状態だけが格納されるため、直列化復元されたオブジェクトは空のスレッド・キューを保持します。 直列化可能性が必要な標準的なサブクラスは、直列化復元時にこれを既知の初期状態に復元するreadObjectメソッドを定義します。

使用法

このクラスをシンクロナイザの基盤として使用するには、必要に応じて、getState()setState(int)、またはcompareAndSetState(int, int)を使用して同期状態を検査または変更することによって次のメソッドを再定義します。

これらの各メソッドは、デフォルトではUnsupportedOperationExceptionをスローします。 これらのメソッドの実装は、内部がスレッド・セーフでなければならず、また、通常は短くかつブロッキングなしである必要があります。 これらのメソッドの定義は、このクラスのサポートされる唯一の使用方法です。 ほかのメソッドはすべて、個別に変更することはできないため、finalと宣言されます。

また、AbstractOwnableSynchronizerから継承されたメソッドが、排他的なシンクロナイザを所有するスレッドを追跡するときに便利です。 ロックを保持するスレッドを判断するのに役立つ監視および診断のツールが有効になるため、これらを使用することをお薦めします。

このクラスは内部のFIFOキューに基づくとはいえ、自動的にFIFO取得ポリシーが適用されるわけではありません。 排他的な同期のコアは、次の形式になります。

 Acquire:
     while (!tryAcquire(arg)) {
        enqueue thread if it is not already queued;
        possibly block current thread;
     }

 Release:
     if (tryRelease(arg))
        unblock the first queued thread;
 
(共有モードも似ているが、カスケード信号が関係する場合がある)

取得でのチェックはキューに入れられる前に呼び出されるため、新しく取得するスレッドは、ブロックされてキューに入れられる他のスレッドより先にバージできます。 ただし、必要であれば、tryAcquiretryAcquireSharedを定義して、1つ以上の検査メソッドを内部的に呼び出し、公平なFIFO取得順序を提供することによってバージを無効にするようにできます。 特に、ほとんどの公平なシンクロナイザは、hasQueuedPredecessors() (公平なシンクロナイザで使用されるように特別に設計されたメソッド)がtrueを返す場合はfalseを返すようにtryAcquireを定義できます。 その他のバリエーションも可能です。

通常、スループットとスケーラビリティは、デフォルトのバージ(貪欲放棄護送回避とも呼ばれる)ストラテジの最上位に位置します。 これが偏りがなく、枯渇しないことは保証されませんが、先にキューに入れられたスレッドは、あとでキューに入れられるスレッドより前に再競合が許可され、各再競合は着信するスレッドに対して成功する公平な機会を保持します。 また、取得は、通常の意味では「スピン」しませんが、ブロッキング前に他の計算に挿入されたtryAcquireの複数の呼出しを実行可能です。 これにより、排他的な同期が短期的に保持されるだけの場合、スピンの恩恵の多くを享受できます。さらに、排他的な同期が保持されない場合にはほとんど負担なしで、その恩恵を享受できます。 必要に応じて、取得メソッドを呼び出す前に「早道」チェックを行うことで、これを補強できます。たとえば、hasContended()hasQueuedThreads()を事前にチェックし、シンクロナイザが競合する可能性が少ない場合のみ取得メソッドを呼び出すようにします。

このクラスは、使用範囲をint状態、パラメータの取得と解放、および内部FIFO待機キューに依存可能なシンクロナイザに限定することにより、効率的で拡張性の高い同期基盤の一部を提供します。 これでも十分ではない場合、atomicクラス、独自のカスタムQueueクラス、およびLockSupportブロック・サポートを使用して、シンクロナイザを低レベルから構築できます。

使用例

次に、値ゼロを使用してロック解除状態およびロック状態を表す再入不可能な相互排他ロック・クラスを示します。 再入不可能なロックでは、厳密には現在の所有者スレッドを記録する必要はありませんが、このクラスでは使用状況を簡単に監視できるように、記録を行います。 また、条件をサポートし、いくつかの計測メソッドを公開します:

 
 class Mutex implements Lock, java.io.Serializable {

   // Our internal helper class
   private static class Sync extends AbstractQueuedSynchronizer {
     // Acquires the lock if state is zero
     public boolean tryAcquire(int acquires) {
       assert acquires == 1; // Otherwise unused
       if (compareAndSetState(0, 1)) {
         setExclusiveOwnerThread(Thread.currentThread());
         return true;
       }
       return false;
     }

     // Releases the lock by setting state to zero
     protected boolean tryRelease(int releases) {
       assert releases == 1; // Otherwise unused
       if (!isHeldExclusively())
         throw new IllegalMonitorStateException();
       setExclusiveOwnerThread(null);
       setState(0);
       return true;
     }

     // Reports whether in locked state
     public boolean isLocked() {
       return getState() != 0;
     }

     public boolean isHeldExclusively() {
       // a data race, but safe due to out-of-thin-air guarantees
       return getExclusiveOwnerThread() == Thread.currentThread();
     }

     // Provides a Condition
     public Condition newCondition() {
       return new ConditionObject();
     }

     // Deserializes properly
     private void readObject(ObjectInputStream s)
         throws IOException, ClassNotFoundException {
       s.defaultReadObject();
       setState(0); // reset to unlocked state
     }
   }

   // The sync object does all the hard work. We just forward to it.
   private final Sync sync = new Sync();

   public void lock()              { sync.acquire(1); }
   public boolean tryLock()        { return sync.tryAcquire(1); }
   public void unlock()            { sync.release(1); }
   public Condition newCondition() { return sync.newCondition(); }
   public boolean isLocked()       { return sync.isLocked(); }
   public boolean isHeldByCurrentThread() {
     return sync.isHeldExclusively();
   }
   public boolean hasQueuedThreads() {
     return sync.hasQueuedThreads();
   }
   public void lockInterruptibly() throws InterruptedException {
     sync.acquireInterruptibly(1);
   }
   public boolean tryLock(long timeout, TimeUnit unit)
       throws InterruptedException {
     return sync.tryAcquireNanos(1, unit.toNanos(timeout));
   }
 }

次に、起動に単一のsignalを必要とすることを除き、CountDownLatchクラスに類似したラッチ・クラスを示します。 ラッチは非排他的であるため、shared取得および解放メソッドを使用します。

 
 class BooleanLatch {

   private static class Sync extends AbstractQueuedSynchronizer {
     boolean isSignalled() { return getState() != 0; }

     protected int tryAcquireShared(int ignore) {
       return isSignalled() ? 1 : -1;
     }

     protected boolean tryReleaseShared(int ignore) {
       setState(1);
       return true;
     }
   }

   private final Sync sync = new Sync();
   public boolean isSignalled() { return sync.isSignalled(); }
   public void signal()         { sync.releaseShared(1); }
   public void await() throws InterruptedException {
     sync.acquireSharedInterruptibly(1);
   }
 }

導入されたバージョン:
1.5
関連項目: