AbstractQueuedSynchronizer の導入と使用

AbstractQueuedSynchronizer は、concurrent パッケージのコア クラスであり、LockSupport および Unsafe に基づいて実装されます。
AQS で使用される主なメソッドは継承です. クラスを作成して AbstractQueuedSynchronizer を継承し、特定のメソッドを書き換えて、さまざまな機能を持つ同期コンポーネントを実装します。

1. 同期コンポーネントの概要

同期コンポーネントは、一般に次の 2 つのタイプに分けることができます。

排他的同期コンポーネント (排他的ロックとも呼ばれます):常に 1 つのスレッドのみがロックを取得して実行できますが、他のスレッドはブロックされて待機キューに入ります。

共有同期コンポーネント (共有ロックとも呼ばれます):共有ロックを使用すると、複数のスレッドを同時に実行できます. 通常、共有ロックは内部で複数の実行権限を維持します. 各スレッドは実行時にライセンスを取得し、終了時にライセンスを解放します ,共有ロックに権限がない場合、スレッドは待機キューに入ります

注: 排他ロックは、共有ロックの特殊なケースと見なすことができ、許可が 1 つしかありません。
この観点から、排他ロックと共有ロックの両方が、アクセス許可を取得および解放する機能を提供する必要があります

ライセンスの取得:スレッドがライセンスを正常に取得した場合、実行する権利があり、ライセンスの数 -1 です。取得に失敗した場合、スレッドはブロックされ、
ライセンスを解放するために待機キューに入ります: スレッドが実行された後ライセンスの実行が終了すると、ライセンスが解放され、ライセンス数が+1されます. 同時に、待機キューのスレッドを起床させる役割を担います.

ライセンスの取得とライセンスの解放のプロセスでは、維持する必要がある 2 つの重要な要素があることがわかります。使用可能なライセンスの数と待機キューです。

2. AQS は同期コンポーネントの原則をサポートします

キュー シンクロナイザー AbstractQueuedSynchronizer は、同期コンポーネントを構築するための基本的なフレームワークとして機能します。同期ステータスの排他取得に対応し、同期ステータスの共有取得にも対応しています。ReentryLock、ReentryReadWriteLock、CountDownLatch などはすべて AQS に基づいて実装されています。

AQS は、使用可能なライセンス数と待機キューの両方をサポートします。

使用可能なライセンス数のサポート:実行ライセンスを表すために int 型の状態変数を使用します. 初期状態は 0 であり, ライセンスの数を 5 に制限します. スレッドがライセンスを取得するとき, state+1 はいくつかのライセンスは待機キューで使用されています
.サポート:組み込みの FIFO キューを介してスレッドのキューイング作業を完了し、キューのノードは静的内部クラス Node によって実現されます

AQS は待機キューを完全にサポートすることに注意してください。つまり、キューに出入りするスレッドをブロックする操作の詳細から開発者を完全に保護しますが、利用可能な数量 (状態) を部分的にサポートします。開発者がリセットする 適切に機能する特定のメソッドを書く

AQS では 8 つのテンプレート メソッドが定義されており、開発者がオーバーライドする必要がある 4 つのメソッドに対応しています。

コンポーネントタイプ テンプレート法 オーバーライドする必要があるメソッド
排他的同期コンポーネント void 取得 (int 引数) boolean tryAcquire(int arg)
void acquireInterruptably(int arg)
boolean tryAcquireNanos(int arg, long nanosTimeout)
boolean release(int arg) boolean tryRelease(int arg)
共有同期コンポーネント void acquireShared(int arg) boolean tryAcquireShared(int arg)
void acquireSharedInterruptably(int arg)
boolean tryAcquireSharedNanos(int arg, long nanosTimeout)
boolean releaseShared(int arg) boolean tryReleaseShared(int arg)

3. 同期コンポーネントを書く際の注意点

1) 新しいインターフェイスと実装を使用して同期コンポーネントをラップする: たとえば、同期コンポーネントを作成する場合、AQS を継承する Sync を想定して、排他ロックを実装したいと考えています。Sync クラスで tryRelease と tryAcquire をオーバーライドするだけで済みますが、AQS を継承する場合、tryAcquireShared や tryReleaseShared などの共有ロック メソッドも継承されます。Sync は、これらの共有同期コンポーネントのメソッドを実装していません。これは、Sync が単なる排他ロックであるためです。ビジネスの観点から、これらのメソッドは、ユーザーの誤操作を防ぐためにシールドする必要があります。最良の実装によると、シールド メソッドは、Mutex によって表されると仮定して、新しいインターフェイスを定義することです。このインターフェイスは、排他ロック関連のメソッドのみを定義し、クラス MutexImpl を記述して Mutex インターフェイスを実装し、Mutex の操作を行います。同期コンポーネントの Sync クラス。すべて MutexImpl にカプセル化されています。

2) 同期コンポーネントは、静的な内部クラスとして定義することをお勧めします。同期コンポーネントは通常、特定の目的のために実装されるため、特定の場合にのみ適用される場合があります。同期コンポーネントがユニバーサルでない場合は、プライベートな静的内部クラスとして定義する必要があります。最初のポイントと組み合わせると、作成した同期コンポーネント Sync は、MutexImpl のプライベートな静的内部クラスである必要があります。

4. AQSによる排他ロックの実装例

public interface Mutex {
    
    

    void acquire();

    void release();
}
public class MutexImpl implements Mutex {
    
    

    private Sync sync = new Sync();

    @Override
    public void acquire() {
    
    
        sync.acquire(1);
    }

    @Override
    public void release() {
    
    
        sync.release(1);
    }

    private static class Sync extends AbstractQueuedSynchronizer {
    
    

        @Override
        protected boolean tryAcquire(int arg) {
    
    
            return super.compareAndSetState(0, 1);
        }

        @Override
        protected boolean tryRelease(int arg) {
    
    
            return super.compareAndSetState(1, 0);
        }


    }
}
public class Main {
    
    

    public static void main(String[] args) {
    
    
        Mutex mutex = new MutexImpl();
        for (int i = 0; i < 10; i++) {
    
    
            new Thread(new MutexThread(mutex)).start();
        }
    }
}

class MutexThread implements Runnable {
    
    

    private Mutex mutex;

    public MutexThread(Mutex mutex) {
    
    
        this.mutex = mutex;
    }

    @Override
    public void run() {
    
    
        String name = Thread.currentThread().getName();
        mutex.acquire();
        System.out.println(name + "获得锁开始执行");
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        } finally {
    
    
            mutex.release();
            System.out.println(name + "释放锁结束运行");
        }
    }
}

以下の結果:

Thread-0获得锁开始执行
Thread-1获得锁开始执行
Thread-0释放锁结束运行
Thread-2获得锁开始执行
Thread-1释放锁结束运行
Thread-2释放锁结束运行
Thread-4获得锁开始执行
Thread-4释放锁结束运行
Thread-3获得锁开始执行
Thread-3释放锁结束运行
Thread-5获得锁开始执行
Thread-5释放锁结束运行
Thread-6获得锁开始执行
Thread-6释放锁结束运行
Thread-7获得锁开始执行
Thread-7释放锁结束运行
Thread-8获得锁开始执行
Thread-8释放锁结束运行
Thread-9获得锁开始执行
Thread-9释放锁结束运行

公式アカウントのアルゴリズムのニッチに注目してください

おすすめ

転載: blog.csdn.net/SJshenjian/article/details/130303091