Java同時実行のCASおよびAQS

CAS(コンペアアンドスワップ)

場合

比較と交換は、マルチスレッドでのロックの使用によって引き起こされるパフォーマンス低下の解決策です。CAS操作には3つのオペランドが含まれます-V(メモリ値)A(期待される元の値)B(新しい値-更新された値)、メモリ値の場合V =期待される元の値。これは、値が変更されていないことを意味します。値はBに更新されます。それ以外の場合、処理は行われません。最新の値が返されます。

Javaで使用されるUnsafeクラスは、CASを実装するためのハードウェアレベルのアトミック操作を提供します。

AtomicIntegerでのCASの使用

// 返回旧值,并设置新值, 这里是调用的 unsafe的getAndSetInt方法
   public final int getAndSet(int newValue) {
    
    
        return unsafe.getAndSetInt(this, valueOffset, newValue);
    }
    ...
 //  看下unsafe 的方法
    public final int getAndSetInt(Object var1, long var2, int var4) {
    
    
        int var5;
        do {
    
    
            var5 = this.getIntVolatile(var1, var2);
            // 这里 使用了 循环 进行 CAS操作
        } while(!this.compareAndSwapInt(var1, var2, var5, var4));

        return var5;
    }
   ...
   //通过cas原子性的将值设置为更新值,也是使用了 unsafe中的方法
    public final boolean compareAndSet(int expect, int update) {
    
    
        return unsafe.compareAndSwapInt(this, valueOffset, expect, update);
    }

通常の状況では、スレッドの競合がそれほど激しくない場合、CASはロック方法よりも効率的ですが、ループが飛び出さないか、長時間正常に設定できないと、CPUが無駄になります。

ABA問題

CASを使用するとABA問題が発生します。つまり、メモリ内の値が変更されたが元に戻されました。CAS操作を実行すると、メモリの値は期待値と同じですが、実際には、変更後に値が元に戻りました。これはABAの問題です。解決策は、バージョン番号を追加することです。
例えば

A(メモリ値)-B(期待値)-C(新しい値)
はA1-B1 -C1になるため、AがBと等しい場合に比較し、バージョン番号を比較して整合性を保つように操作します。バージョン番号に整合性がある場合、Cに更新し、Aバージョン番号を追加します。

AQS(AbstractQueuedSynchronizer)

AQSは、FIFO待機キューに基づいてブロッキングロックおよび関連するシンクロナイザーを実装するためにJDKによって提供される内部フレームワークです。

先入れ先出し(FIFO)待機キューに依存するブロッキングロックおよび関連するシンクロナイザー(セマフォ、イベントなど)を実装するためのフレームワークを提供します。このクラスは、状態を表すために単一のアトミックint値に依存するほとんどのタイプのシンクロナイザーの有用な基礎として設計されています。サブクラスは、この状態を変更するための保護されたメソッドを定義し、オブジェクトが取得されたか解放されたかに基づいて状態の意味を定義する必要があります。これらを前提として、このクラスの他のメソッドは、すべてのキューイングおよびブロッキングメカニズムを実行します。サブクラスは他の状態フィールドを保持できますが、値を操作するメソッドを使用してintをアトミックに更新するだけで、getState()、setState(int)、compareAndSetState(int、int)が同期に関連して追跡されます。
サブクラスは、その閉じたクラスの同期プロパティを実装するために、非公開の内部ヘルパークラスとして定義する必要があります。AbstractQueuedSynchronizerクラスは、同期インターフェイスを実装していません。代わりに、acquireInterruptibly(int)などのいくつかのメソッドを定義します。これらのメソッドは、特定のロックおよび関連するシンクロナイザーを介してパブリックメソッドを適切に実行するために呼び出すことができます。
このクラスは、デフォルトの排他モードと共有モードをサポートします。排他モードで取得する場合、他のスレッドを介して取得しようとしても成功しません。複数のスレッドによって取得された共有モードは成功する可能性があります(ただし、成功する必要はありません)。機械的な意味を除いて、このクラスはこれらの違いを理解しません。共有モードが正常に取得されると、次の待機スレッド(存在する場合)も取得できるかどうかを判断する必要があります。異なるモードで待機しているスレッドは、同じFIFOキューを共有します。通常、実装サブクラスはこれらのモードの1つのみをサポートしますが、両方がReadWriteLockで役割を果たすことができます。排他的または共有のみのモードのみをサポートするサブクラスは、未使用のモードをサポートするメソッドを定義する必要はありません。
このクラスによって定義されたネストされたAbstractQueuedSynchronizer.ConditionObjectは、サブクラスによってサポートされる条件のタイプとして使用でき、メソッドisHeldExclusively()の実装の排他モードをサポートします。現在のスレッドにとどまることに関連して同期され、排他的であるかどうかを報告します。メソッドrelease(int)と現在の呼び出しgetState()値はこの目的を完全に解放し、acquire(int)は、この保存された状態値を指定して、最終的にこのオブジェクトを以前に取得した状態に復元します。AbstractQueuedSynchronizerメソッドはそのような条件を作成するため、この制約を満たすことができない場合は使用しないでください。もちろん、AbstractQueuedSynchronizer.ConditionObjectの動作は、そのシンクロナイザー実装のセマンティクスに依存します。
このクラスは、内部キューの検査、検出、および監視メソッドと、条件オブジェクトの同様のメソッドを提供します。これらは、同期メカニズムにAbstractQueuedSynchronizerを使用して、必要に応じてクラスにエクスポートできます。
このタイプのシリアル化では、状態を維持するために基になるアトミック整数のみが格納されるため、逆シリアル化されたオブジェクトには空のスレッドキューがあります。シリアル化可能である必要がある一般的なサブクラスは、readObjectメソッドを定義します。readObjectメソッドは、readObjectの時点で既知の初期状態に復元できます。

状態状態
AbstractQueuedSynchronizerは、同期状態を表すために、揮発性の変更されたint変数状態を維持します。Volatileは、状態のマルチスレッド環境での可視性を保証します。そして、状態に対する操作はすべてアトミックです

   /**
     * The synchronization state.
     */
    private volatile int state;
    
    protected final int getState() {
    
    
        return state;
    }
    protected final void setState(int newState) {
    
    
        state = newState;
    }
    protected final boolean compareAndSetState(int expect, int update) {
    
    
        return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
    }

AQSのカスタム共有方法

AQSは、排他的および共有リソース共有方法に分けられます。
排他的(排他的、ReentrantLockなどの1つのスレッドのみを実行できます)と共有(共有、Semaphore / CountDownLatchなどの複数のスレッドを同時に実行できます)
カスタムシンクロナイザーは実装時に実装共有リソース状態の取得と解放を実現することだけが必要です。特定のスレッド待機キューのメンテナンス(リソースのキューへの取得の失敗/ウェイクアップとキューからの離脱など)については、など)、AQSはトップレベルで実装されています。以下のメソッドは、主にカスタムシンクロナイザーを実装するときに実装されます。

isHeldExclusively():スレッドがリソースを独占しているかどうか。それを実現するために条件を使用するだけです。
tryAcquire(int):排他モード。リソースの取得を試みます。成功した場合はtrueを返し、失敗した場合はfalseを返します。
tryRelease(int):排他モード。リソースの解放を試みます。成功した場合はtrueを返し、失敗した場合はfalseを返します。
tryAcquireShared(int):共有メソッド。リソースを取得してみてください。負の数は失敗を意味し、0は成功を意味しますが、使用可能なリソースは残っていません。正の数は成功を意味し、残りのリソースがあります。
tryReleaseShared(int):共有メソッド。リソースを解放してみてください。解放後に後続の待機中のノードをウェイクアップできる場合はtrueを返し、そうでない場合はfalseを返します。

ソースコード
1.acquire(int)
acquireは、リソースを排他的に取得する方法です。リソースが取得された場合、スレッドは直接実行に戻ります。そうでない場合、スレッドは待機キューに入り、ブロックされ、スレッドは繰り返しブロックおよびブロック解除される可能性があります。プロセス全体がスレッドの中断を無視する影響:このメソッドは、排他モードで共有リソースを取得するためのスレッドの最上位エントリです。リソースが取得された後、スレッドはクリティカルセクションコードを実行できます。

    public final void acquire(int arg) {
    
    
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

tryAcquire(arg)はリソースの取得を試み、成功した場合は戻ります。これは
、待機キューの最後にスレッドを追加し、排他モードとしてマークするために自分で実装する必要がある
addWaiter()です。acquireQueued()スレッドは待機キュー内のリソースを取得します。常にリソースを取得した後にのみ戻ります。待機プロセス全体で中断された場合はtrueを返し、それ以外の場合はfalseを返します。
selfInterrupt()、割り込みを追加

メインロジック:サブクラスによって実装されたtryAcquireメソッドを呼び出すことにより、このメソッドは同期状態のスレッドセーフな取得を保証します。同期状態の取得が失敗した場合は、addWaiter()を呼び出して、排他的に同期キューの最後にスレッドを追加します。モード、最後にacquireQueued()メソッドを呼び出します。

おすすめ

転載: blog.csdn.net/xiaodujava/article/details/101526074