Java 同時実行パッケージのロック パッケージのロックは基本的に紹介されましたが、シンクロナイザー AQS の動作メカニズムを明確に理解した後、この章では別のロックに焦点を当てます。重要なロック - ReentrantReadWriteLock 読み取り/書き込みロック。
ReentrantLock は排他ロックです。つまり、ロックは 1 つのスレッドによってのみ取得できます。しかし、スレッドが読み取り操作のみを実行するシナリオの場合はどうなるでしょうか。このように、ReentrantLock は、読み取りスレッドのセキュリティを確保する必要がなく、この方法でのみパフォーマンスと効率を最大限に保証できます。 ReentrantReadWriteLock は、読み取りロックと書き込みロックに分かれており、書き込みロックは 1 つの書き込み操作スレッドのみが取得できます。 lock は共有ロック (AQS の共有モード)、読み取りロックは排他ロック (AQS の排他モード) です。まず、読み取り/書き込みロックのインターフェイス クラスを見てみましょう:
1 public interface ReadWriteLock { 2 Lock readLock(); //获取读锁3 Lock writeLock(); //获取写锁4 }
ReadWriteLock インターフェイスには、読み取りロックを取得するメソッドと書き込みロックを取得するメソッドの 2 つのメソッドのみが定義されていることがわかります。以下はReadWriteLock - ReentrantReadWriteLockの実装クラスです。
ReentrantLock と同様に、ReentrantReadWriteLock も内部クラス Sync を通じてシンクロナイザー AQS を実装し、公平なロックと不公平なロックを実装します。この考え方は ReentrantLock と似ています。 ReadWriteLock インターフェイスで取得される読み取りロックと書き込みロックはどのように実装されますか?
//ReentrantReadWriteLockprivate final ReentrantReadWriteLock.ReadLock readerLock;private final ReentrantReadWriteLock.WriteLock writerLock;final Sync sync;public ReentrantReadWriteLock(){this(false); //默认非公平锁}public ReentrantReadWriteLock(boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); //锁类型(公平/非公平)readerLock = new ReadLock(this); //构造读锁writerLock = new WriteLock(this); //构造写锁} ……public ReentrantReadWriteLock.WriteLock writeLock0{return writerLock;}public ReentrantReadWriteLock.ReadLock readLock0{return ReaderLock;}
//ReentrantReadWriteLock$ReadLockpublic static class ReadLock implements Lock {protected ReadLock(ReentrantReadwritLock lock) { sync = lock.sync; //最后还是通过Sync内部类实现锁 } …… //它实现的是Lock接口,其余的实现可以和ReentrantLock作对比,获取锁、释放锁等等}
//ReentrantReadWriteLock$WriteLockpublic static class WriteLock implemnts Lock {protected WriteLock(ReentrantReadWriteLock lock) { sync = lock.sync; } …… //它实现的是Lock接口,其余的实现可以和ReentrantLock作对比,获取锁、释放锁等等}
上記は ReentrantReadWriteLock の概要です。実際には、その中に ReadLock と WriteLock という 2 つの内部クラスがあることがわかります。どちらのロックも Self-Lock インターフェイスを実装しており、ReentrantLock と比較できます。これら 2 つのロックの内部実装は、シンクロナイザー AQS である Sync を通じて実装されます。これは、ReentrantLock の Sync と比較することもできます。
AQS を振り返ると、内部には 2 つの重要なデータ構造があります。1 つは同期キューで、もう 1 つは同期状態です。この同期状態は、読み取り/書き込み状態である読み取り/書き込みロックに適用されます。 AQS では状態整数が 1 つだけです。 同期ステータスを表すには、読み取り/書き込みロックでの読み取りと書き込みの 2 つの同期ステータスを記録する必要があります。したがって、読み取り/書き込みロックは AQS で状態整数を処理します。これは合計 4 バイトと 32 ビットの int 変数であり、読み取り状態と書き込み状態はそれぞれ 16 ビットを占有します。上位 16 ビットは読み取りを表します。 16 ビットは書き込みを示します。
ここで、state の値が 5 の場合、バイナリは (00000000000000000000000000000101) になります。これには、変位操作を使用する必要があります。計算方法は、書き込み状態 state & 0x0000FFFF、読み取り状態 state >>> 16 です。書き込み状態を 1 増やすことは状態 + 1 に等しく、読み取り状態を 1 増やすことは状態 + (1
これまでの経験に基づいて、AQS はロックを取得するためのアルゴリズム スケルトンをすでに設定しており、tryAcquire (排他的ロック) を実装するためにサブクラスのみが必要であることがわかります。 tryAcquire を確認します。
1 //ReentrantReadWriteLock$Sync 2 protected final boolean tryAcquire(int acquires) { 3 Thread current = Thread.currentThread; 4 int c = getState(); //获取state状态 5 int w = exclusiveCount(c); //获取写状态,即 state & 0x00001111 6 if (c != 0) { //存在同步状态(读或写),作下一步判断 7 if (w == 0 || current != getExclusiveOwnerThread()) //写状态为0,但同步状态不为0表示有读状态,此时获取锁失败,或者当前已经有其他写线程获取了锁此时也获取锁失败 8 return false; 9 if (w + exclusiveCount(acquire) > MAX_COUNT) //锁重入是否超过限制10 throw new Error(“Maxium lock count exceeded”);11 setState(c + acquire); //记录锁状态12 return true;13 }14 if (writerShouldBlock() || !compareAndSetState(c, c + acquires))15 return false; //writerShouldBlock对于非公平锁总是返回false,对于公平锁则判断同步队列中是否有前驱节点16 setExclusiveOwnerThread(current);17 return true;18 }
上記は書き込みロックのステータス取得ですが、わかりにくいのは上記で説明したように、Unfair ロックの場合は false を返すメソッドですが、Fair ロックの場合は次のように hasQueuedPredecessors メソッドが呼び出されます。 :
その理由は何ですか?これは、不公平なロックと公正なロックの違いに戻ります。詳細については、「5. ロック インターフェイスとその実装 ReentrantLock」を参照してください。不公平なロックの場合、スレッドはロックを取得するたびに、同期キューにスレッドがあるかどうかに関係なく、まずロックの取得操作を強制します。取得できない場合、スレッドはキューの最後まで構築されます。同期キューが存在する限り、公平なロックの場合。キュー内にスレッドがある場合、ロックは取得されませんが、スレッド構造はキューの最後に追加されます。書き込みステータスの取得に戻ります。tryAcquire メソッドでは、ロックを保持しているスレッドがないことがわかりましたが、この時点では、不公平なロックの場合はロックの取得、公正なロックの場合は、対応する操作が異なるロックに従って実行されます。 - 同期キュー スレッド内にスレッドがあり、ロックの取得がなく、キューの最後に追加されます。
書き込みロックの解放プロセスは、基本的に ReentrantLock の解放プロセスと似ています。結局のところ、これらは排他ロックであり、各解放は書き込みロックが完全に解放されたことを意味する 0 になるまで減少します。
同様に、これまでの経験に基づいて、AQS はロックを取得するためのアルゴリズム スケルトンをすでにセットアップしており、tryAcquireShared (共有ロック) を実装するためにサブクラスのみが必要であることがわかります。 ) なので、tryAcquireShared をチェックするだけで済みます。共有モードのロックでは、複数のスレッドが同時にロックを取得できることがわかっています。ここで、T1 スレッドがロックを取得し、このとき、T2 も同期状態を取得します。 lock、state=2、次に T1 スレッド Re-entry state=3。これは、読み取り状態がすべてのスレッドの読み取りロック回数の合計であり、各スレッドが読み取りロックを取得できる回数は 2 回のみであることを意味します。 ThreadLock に保存され、スレッド自体によって維持されるため、ここでいくつかの処理を実行する必要があります。複雑な処理があり、ソース コードは少し長くなりますが、複雑なのは、各スレッドが読み取りロックを取得する回数を保存するという事実にあります。詳細については、ソースコードの tryAcquireShared を参照して、上記の書き込みロック取得の分析と組み合わせて理解してください。
読み取りロックの解放で注目すべき点は、それ自体が維持するロックの取得数と、シフト操作による状態stateの減少 – (1
以上がReadWriteLock インターフェースとその実装 ReentrantReadWriteLockの詳細内容です。詳細については、PHP 中国語 Web サイトの他の関連記事を参照してください。