Java同時AQS:概要


キューシンクロナイザAbstractQueuedSynchronizer(AQS、略称シンクロナイザ)は、ロックまたは他の同期コンポーネントを構築するための基礎フレームワークであり、intメンバー変数を使用して同期状態を表し、内蔵FIFOキューを通じてスレッドのキュー作業を完了し、シンクロナイザを理解すれば、Javaおよび発注中の他の同時コンポーネントをより深く理解することができる.そして発注した著者(Doug Lea)は,大部分の同期需要を実現する基礎になることを期待している.
------『Java同時プログラミングの芸術』
——————————————私は分割線—————————————————————————————————
このブログではまずAQSについて紹介し、APIの一例を利用して深く理解します.
シンクロナイザとは
APIのAQSの定義を参照するには、まずこの概念を導入します.
先進的なプリフェッチ(FIFO)待ち行列に依存するブロッキングロックおよび関連する同期器(信号量、イベントなど)を実現するためのフレームワークが提供される.このような設計目標は,単一原子int値に依存して状態を表すほとんどの同期器の有用な基礎となることである.サブクラスは、このステータスを変更する保護メソッドを定義し、このオブジェクトに対して取得または解放を意味するステータスを定義する必要があります.これらの条件を仮定すると、このような他の方法は、すべてのキューおよびブロックメカニズムを実現することができる.サブクラスは他のステータスフィールドを維持できますが、同期を取得するためにgetState()、setState(int newState)、compareAndSetState(int except,int update)メソッドを使用して原子的に更新されたint値を操作するだけです.
このようなサポートは、デフォルトの独占モードと共有モードのいずれか、または両方をサポートします.排他モードでは、他のスレッドがロックを取得しようとすると成功しません.共有モードでは、複数のスレッドがロックを取得することに成功する可能性があります(ただし、必ずしもそうではありません).このような違いは、共有モードでロックが正常に取得された場合、次の待機スレッド(存在する場合)も、ロックが正常に取得されるかどうかを機械的に認識することに加えて、これらの違いを「理解」する必要はありません.異なるモードでの待ちスレッドは、同じFIFOキューを共有することができる.通常、実装サブクラスはいずれかのモードのみをサポートするが、両方のモードは、例えば、ReadWriteLockにおいて機能することができる.排他モードのみをサポートするサブクラスまたは共有モードのみをサポートするサブクラスは、未使用モードをサポートする方法を定義する必要はありません. 
重点を置く:
1、同期器の主な実現方式は継承であり、サブクラスは同期器を継承し、その抽象的な方法を実現することによって同期状態を管理し、すなわち上述した単一原子int値を利用して同期状態を管理する.
2、サブクラスで同期器が指定した方法を書き換える場合、同期器が提供する3つの方法:getState()setState(int newState)、compareAndSetState(int except,int update)を使用して同期状態にアクセスまたは変更する必要があります.もちろん、この3つの同期状態を管理する方法以外にも多くの方法がありますが、以下で説明します.注意してください.ここで同期状態を変更するときは、CASを使用します.これは状態の変更が安全であることを保証するためです.
3、APIでは、サブクラスをカスタム同期コンポーネントとして定義する静的内部クラスを確立する.
4、シンクロナイザは同期コンポーネントの使用のためにいくつかの同期状態の取得と解放方法を定義し、シンクロナイザは独占的に同期状態を取得することをサポートし、共有的な同期状態の取得をサポートすることができる.
同期器はロック(または他の任意の同期コンポーネント)を実現する鍵であり、ロックに同期器を集約し、同期器を利用してロックの意味を実現する.両者の関係は、ロックはユーザ向けであり、ユーザとロックが相互作用するインタフェースを定義し、例えば2つのスレッドの並列アクセスを許可し、実装の詳細を隠すことができる.同期器はロックに向いているのは暇で、それはロックの実現方式を簡略化して、ロックの実現過程の同期状態管理、スレッドのキュー、スレッドの待機と起動などの下層操作を遮蔽しました.
シンクロナイザで使用できるいくつかの方法:
  • boolean tryAcquire(int arg):独占的に同期状態を取得するには、現在の状態を問合せ、同期状態が予想に合致するかどうかを判断してからCASで同期状態を設定する必要があります.
  • boolean tryRelease(int arg):同期状態を独占的に解放し、同期状態の取得を待つスレッドは同期状態を取得する機会がある.
  • int tryAcquireShared(int arg):共有で同期状態を取得し、戻り値が0以上で、取得に成功したことを示し、そうでなければ取得に失敗した.
  • boolean tryReleaseShared(int arg):共有解放同期状態;
  • boolean isHeldExclusively():現在の同期器が排他モードでスレッドによって占有されているかどうか、一般的にこの方法は現在のスレッドによって排他されているかどうかを示す.

  • 以上の方法は、同期器が提供する書き換え方法であり、後の方法はいずれもテンプレート方法であり、同期コンポーネントをカスタマイズする際に同期器が提供するテンプレート方法を使用して独自の同期意味を実現する例である.
  • void acquire(int arg):独占的に同期状態を取得し、現在のスレッドが同期状態を取得することに成功した場合、このメソッドによって返されます.そうしないと、同期キュー待機に入り、書き換え可能なtryAcquire(int arg)メソッドが呼び出されます.
  • void acquireInterruptibly(int arg):acquire(int arg)と同じですが、このメソッドは割り込みに応答し、現在のスレッドは同期状態を取得せずに同期キューに入り、現在のスレッドが割り込まれた場合、このメソッドはInterruptedExceptionを投げ出して戻ります.
  • boolean tryAcquireNanos(int arg,long nanos):同期状態をタイムアウトして取得し、現在のスレッドがnanos時間内に同期状態を取得していない場合はfalseに戻り、取得したらtrueに戻る.
  • void acquireShared(int arg):共有式は同期状態を取得し、現在のスレッドが同期状態を取得していない場合、同期キュー待機に入り、独占式との主な違いは、同じ時点で複数のスレッドが同期状態を取得できることである.
  • void acquireSharedInterruptibly(int arg):acquireShared(int arg)と同様に、この方法は中断に応答する.
  • boolean tryAcquireSharedNanos(int arg,long nanostimeout):acquireSharedInterruptibly(int arg)に基づいてタイムアウト制限を追加します.
  • boolean release(int arg):同期状態を独占的に解放します.この方法は、同期状態を解放した後、同期キュー内の最初のノードに含まれるスレッドを起動します.
  • boolean releaseShared(int arg):共有解放同期状態;

  • 上のreleaseメソッドではノードについて説明していますが、後続の同期キュー実装では深く分析されます.ここでは、次のように説明します.
    AQSは内部の同期キュー(FIFO双方向キュー)に依存して同期状態の管理を完了し、現在のスレッドが同期状態の取得に失敗すると、同期器は現在のスレッドや待機状態などの情報をノード(Node)として構築し、同期キューに追加し、現在のスレッドをブロックし、同期状態が解放されると、最初のノードのスレッドが起動し、同期状態を再試行する.
    独占ロックの例を用いて,同期器の動作原理を深く理解した.名前の通り、独占ロックとは、同じ時点で1つのスレッドのみがロックを取得することであり、他のロックを取得するスレッドは同期キューにしか存在せず、ロックを取得したスレッドだけがロックを解放し、後続のスレッドだけがロックを取得することができる.
    /**
     * @author Administrator
     *
     */
    public class Mutex implements Lock {
    	// , 
    	private static class Sync extends AbstractQueuedSynchronizer{
    		// ,1 ,0 
    		protected boolean isHeldExclusively() {
    			return getState() == 1;
    		}
    		// 0 
    		public boolean tryAcquire(int acquires) {
    			if(compareAndSetState(0,1)) {
    				setExclusiveOwnerThread(Thread.currentThread());
    				return true;
    			}
    			return false;
    		}
    		// , 0
    		protected boolean tryRelease(int releases) {
    			if(getState() == 0) {
    				throw new IllegalMonitorStateException();
    			}
    			setExclusiveOwnerThread(null);
    			setState(0);
    			return true;
    		}
    		// condition, condition condition 
    		Condition newCondition() {
    			return new ConditionObject();
    		}
    	}
    	// , Sync 
    	private final Sync sync = new Sync();
    	@Override
    	public void lock() {
    		sync.acquire(1);
    	}
    
    	@Override
    	public void lockInterruptibly() throws InterruptedException {
    		sync.acquireInterruptibly(1);
    	}
    
    	@Override
    	public boolean tryLock() {
    		return sync.tryAcquire(1);
    	}
    
    	@Override
    	public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
    		return sync.tryAcquireNanos(1, unit.toNanos(time));
    	}
    
    	@Override
    	public void unlock() {
    		sync.release(1);
    	}
    
    	@Override
    	public Condition newCondition() {
    		return sync.newCondition();
    	}
    
    }

    上記の例では、排他ロックMutexは、同じ時点で1つのスレッドのみがロックを占有することを許可するカスタム同期コンポーネントである.Mutexでは、AQSを継承し、排他的な同期状態の取得と解放を実現する静的内部クラスが定義されています.tryAcquire(int acquires)メソッドでは、CAS設定に成功した場合(同期状態を1に設定)、同期状態が取得されたことを表し、tryReleases(int release)では同期状態を0にリセットするだけであり、ユーザがMutexを使用する場合は内部同期器と直接付き合うのではなく、Mutexが提供するメソッドを呼び出し、Mutexの実装ではロックを取得するlock()を例に、メソッドインプリメンテーションで同期器のテンプレートメソッドacquire(int args)を呼び出すだけでよい.
    その後、同期キュー、独占同期状態の取得と解放、共有同期状態の取得と解放、タイムアウト同期状態の取得などの同期器キー部分について実装の観点から分析します.
    —————————————そう、私はまた来た——————————————————————
    参照先:
    方騰飛:『Java同時プログラミングの芸術』