以前に AQS と Unsafe (LockSupport は実際には単純なパッケージです) を完了したので、さまざまなロック、同時実行ツール、およびスレッド プールを調べることができます. 結局、それらはすべて AQS または Unsafe に依存する実装を持っています.
ロック&コンディション
Java SDK 並行パッケージは、 LockとConditionの 2 つのインターフェースを介してモニターを再実装します。ここで、 Lock は相互排除の問題を解決するために使用され、 Condition は同期の問題を解決するために使用されます。
では、なぜ再度実装し直す必要があるのでしょうか。キーワード synchronized を使用するのは良いことではないでしょうか?
大部分は、デッドロックを効果的に回避することです。
その理由は、Synchronized の使用が制限され、非先制状態を破ることが難しいためです。
- ブロッキング タイムアウトはサポートされていません。
- ロックが取得されたかどうかを知ることはサポートされていません。
- ノンブロッキング フェッチはサポートされていません。
- 割り込みはサポートされていません。
- 複数の条件の調整はサポートされていません。たとえば、synchronized はロックの待機中の通知のみをサポートし、並行パッケージの Lock は複数の条件を許可できるため、複数の次元で待機中の通知をサポートします。
Condition インターフェースは、AQS の内部クラスである JDK に 1 つだけ実装されていると言えますConditionObject
。Lock インターフェイスでの Condition の適用は、この実装に基づいています。このクラスについては、 AQS 条件ソース コード分析 (juejin.cn)を参照してください。ここでは詳しく説明しません。
ロックに焦点を当てます。
主に Lock インターフェイスReentrantlock
とReentrantReadWriteLock
。この記事と次の記事では、これら 2 つのクラスのソース コードを分析します。
実際、lock パッケージの下で、JDK は楽観的な読み取り (悲観的な読み取りロック、書き込みロックの 2 つの状態があります) の実装も提供します。読み取りが多く書き込みが少ない場合、StampedLock のパフォーマンスは ReadWriteLock よりも優れています
StampedLock
。ただし、このクラスは Lock インターフェースを実装していません。主な理由は次の 2 つです。
StampedLock
所有権の概念はありません. 簡単に言えば、現在のスレッドによって取得されたロックは、別のスレッドによって解放されます。- 悲観的な読み取りロックと書き込みロックは、条件変数をサポートしていません。
ReentrantLock と AQS の関連付け
AQS は、リソースのキューイングやブロック ウェイクアップなどの操作を定義します。最も重要なリソースのstate
取得、解放、およびリソースの意味はすべて、サブクラスによって実装されます。AQS の場合、操作できるのは単なる int 値です。
ReentrantLock はこのサブクラスです。ReentrantLock では、公平ロックと不公平ロックの 2 種類のセマンティクスを使用できますが、どのようなセマンティクスであっても、基本的に AQS を内部で実装し、状態固有の意味を与え、リソースの取得と解放のメソッドを順番に実装します。
isHeldExclusively()//该线程是否正在独占资源。只有用到 condition (AQS.ConditionObject)才需要去实现它。
tryAcquire(int)//独占方式。尝试获取资源,成功则返回true,失败则返回false。
tryRelease(int)//独占方式。尝试释放资源,成功则返回true,失败则返回false。
tryAcquireShared(int)//共享方式。尝试获取资源。负数表示失败;0表示成功,但没有剩余可用资源;正数表示成功,且有剩余资源。
tryReleaseShared(int)//共享方式。尝试释放资源,成功则返回true,失败则返回false。
詳細については、AbstractQueuedSynchronizer (AQS): コンカレント ツールの基礎 (juejin.cn)を参照してください。
視認性を確保するには?
volatile 関連の事前発生の原則に従います。
Lock と Condition は、実際には AQS を通じて実装されます。AQS には揮発性の状態フィールドがあり、このフィールドはロックおよびロック解除時に読み書きされます。
関連する原則によると:
- 順序の原則; スレッド 1 のビジネス操作は、スレッド 1 のロックのロック解除よりも先に行われます。
- 揮発性の原則に従って、このフィールドへの書き込みは、このフィールドへの読み取りの前に行われます。つまり、スレッド 1 はスレッド 2 がロックするよりも早くロックを解除します。
- パスの原則: スレッド 1 のビジネス操作は、スレッド 2 よりも先にロックされます。
では、AQS とは何かを理解すれば、ReentrantLock
実際にはコンテンツはありません。
キーメソッドは、実際には内部で AQS を呼び出すメソッドだからです。ただし、まだ検討すべき点がいくつかあります. 最も重要なことは、再入可能性の実装と、公平なロックと不公平なロックの違いです。
公平なロックと不公平なロック:
ロックは待ち行列に相当し、スレッドがロックを取得しない場合は待ち行列に入り、スレッドがロックを解除する際には、待ち行列から待ちスレッドを起こす必要があります。
公平なロックの場合、ウェイクアップ戦略は、長時間待っている人をウェイクアップすることです。これは非常に公平です。
不公平なロックの場合、この公平性の保証がなく、待ち時間の短いスレッドから先に起こされる可能性があります。
リエントラントロックの実装
ReentrantLock
と呼ばれる AQS を実装する内部クラスがあります。Sync
これは、上記の AQS メソッドを排他モードで実装します (共有ロックをサポートReentrantLock
していない)。
AQS の state == 0 はリソースが使用可能であることを示すという事実に基づいて、ReentrantLock
state > 0 の定義はスレッドによってロックが保持されていることを意味し、その値は再入力の回数を表します。スレッドの再入可能回数の最大値は、int の最大値 2147483647 であると結論付けることができます。
- 各再入取得、状態 + 1。
- 解放状態 -1;
- 状態が 0 になると、リソースが使用可能になり、スレッドがスクランブルに参加するか、最初にキューに入れられたスレッドがそれを取得するのを待っていることを意味します。
注:
ReentrantLock
では、tryAcquire、tryRelease などのメソッドの取得と解放のパラメーターは両方とも 1 であり、再入可能であることを意味します。
abstract static class Sync extends AbstractQueuedSynchronizer {
/**
* Performs non-fair tryLock. tryAcquire is implemented in
* subclasses, but both need nonfair try for trylock method.
*/
@ReservedStackAccess
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 说明没有线程持有锁, 直接获取到
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
// 如果是当前线程持有锁
int nextc = c + acquires;
// 溢出说明重入次数超标了
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
// 因为是重入,肯定不会有争抢,直接 set 即可
setState(nextc);
return true;
}
return false;
}
@ReservedStackAccess
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
// 只允许持有线程释放锁
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
// 等于 0 的时候, 说明完全释放了锁
free = true;
setExclusiveOwnerThread(null);
}
// 同理不会有争抢, 直接set
setState(c);
return free;
}
}
フェアロック&アンフェアロック
取得方法は1つしかないことがわかり、この方法とAQSのデフォルトが不当取得(リソース取得時に先にリソース取得を試み、取得できない場合はキューイングを開始)であることに基づくとSync
、不公平なロックのみ。nonfairTryAcquire
Sync
Sync
は抽象親クラスであるため、デフォルトの不公平な取得と解放を一様に実装しているため、フェア ロックの実装は実際には単純な呼び出しです。
static final class NonfairSync extends Sync {
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}
したがって、から継承された不公平なロックの実装も必要ですSync
。
static final class FairSync extends Sync {
@ReservedStackAccess
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
// 相对非公平实现, 公平实现只多了这个 : !hasQueuedPredecessors()
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}
不公平な実装と比較して、公正な実装はこれだけです:!hasQueuedPredecessors()
このメソッドの具体的な実装に対する AQS の実装を見ることができます。
簡単に言えば、待機キューをトラバースして、キューに入っているスレッドがあるかどうかを判断することです。キューイングスレッドがある場合は、取得を直接終了し、AQS のキューに移動します。
ReentrantLock
それがどのような種類のロックであるかを判断する方法は?
内部実装が2つあるため、簡単に言えば、親クラスSync
の。代わりに、パラメーターまたはコンストラクターを提供して、呼び出し元がどの実装であるかを決定できるようにするReentrantLock
だけで済みます。
デフォルトは unfair lock です。これにより、スループットが向上し、スレッドのブロックとウェイクアップの数が減ります。
サービスがロックを解放したときに競合するようになったのは、主にスレッドのバッチです。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;public ReentrantLock() {
sync = new NonfairSync();
}
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
}
ロックのパブリック メソッドは、Sync
入手
void lock() {
sync.acquire(1);}
void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);}
public boolean tryLock() {
return sync.nonfairTryAcquire(1);}
boolean tryLock(long timeout, TimeUnit unit) throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
他のいくつかのメソッド (AQS のパブリック メソッドを直接呼び出す、Sync の実装がフェア ロックかアンフェア ロックかを決定する) とは異なり、tryLock
内部のアンフェア ロックの実装を直接呼び出しますnonfairTryAcquire
。
tryLock は割り込みにもタイムアウトにも応答できないため、フェア ロックの待機時間が長くなりがちです。
解放された
public void unlock() {
sync.release(1);}
状態
前述のように、Condition には AQS 内部クラスの実装が 1 つしかないため、Condition を取得するメソッドも に直接委任できますSync
。
統計パブリック メソッドは、直接デリゲートします。Sync
状態には特定の意味(リエントリー回数)が付与されているため、まずこのフィールドに基づいて関連する判断や統計手法が判断または取得されます。
// Methods relayed from outer class
final Thread getOwner() {
return getState() == 0 ? null : getExclusiveOwnerThread();
}
final int getHoldCount() {
return isHeldExclusively() ? getState() : 0;
}
final boolean isLocked() {
return getState() != 0;
}
/**
* Reconstitutes the instance from a stream (that is, deserializes it).
*/
private void readObject(java.io.ObjectInputStream s)
throws java.io.IOException, ClassNotFoundException {
s.defaultReadObject();
setState(0); // reset to unlocked state
}
readObject
実装では、何を使用すればよいかわかりませんが、の逆シリアル化の結果はロックフリーでなければならないReentrantlock
と。
要約する
Reentrantlock
複雑なコンテンツが AQS にあるため、以前はかなり複雑だと思っていました。AQS を理解していれば、Reentrantlock
非常に簡単です。