目次
1. ReentrantLock の使用を開始する
同期と比較すると以下のような特徴があります
- 中断可能
- タイムアウトを設定できる
- フェアロックとして設定可能
- 複数の条件変数のサポート
同期と同様に、どちらも再入可能をサポートします
基本的な文法
//获取锁
reetrantLock.lock();
try{
//临界区
}finally{
//释放锁
reentrantLock.unlock();
}
リエントラント
再入可能とは、スレッドが初めてロックを取得した場合、そのスレッドがロックの所有者となり、再度ロックを取得する権利があることを意味します。非再入可能ロックの場合、2 回目にロックを取得すると、そのロックによってブロックされます。
中断可能
lock.lockInterruptibly()メソッドを使用してロックすると、interruptメソッドによって他のスレッドが中断される可能性があります。例: スレッド a はロックを取得しようとしていますが、このロックはすでにスレッド b によって取得されています。b が割り込みを実行すると、スレッド a が失敗しようとしている取得を待つことなく直接中断できます。無制限の待機を防止し、デッド待機を回避し、デッドロックを軽減するためのメカニズムです
ロックタイムアウト
ブール値を返し、ロックが取得された場合は true を返し、ロックを取得できない場合は false を返す lock.tryLock() メソッドがあります。このメソッドはタイムアウト用の 2 つのパラメータを渡すことができます。最初のパラメータ番号は時間を表し、2番目は単位です。これは、ロックを取得するために tryLock() に行くときに最も待機することを表しており、1 秒の場合は最大 1 秒待機しようとして、ロックを取得する前に false を返します。デッドロックを防ぐためのタイムアウト メカニズムでもあります。
フェアロック
Syn は不公平であり、重量級モニターのブロックされたキューも不公平です。
ReentrantLock はデフォルトでは不公平ですが、構築メソッドを通じてブール値を渡すことで公平に変更できます。
条件変数
syn の条件を満たさないスレッドは 1 つのラウンジにまとめられます
そしてReentranLockは複数のラウンジに対応しており、起床時にラウンジに合わせて起床します
マニュアル:
- await 待機する (前にロックを取得する必要がある)
- await を実行するとロックが解除され、conditionObject を入力して待機します。
- 待機スレッドがウェイクアップ (または中断またはタイムアウト) され、ロックを再競合します。
- 競合ロックが成功した後、待機後に実行を継続します。
新しい ReentranLock が完了したら、newCondition()メソッドを使用してラウンジを作成し、その新しい条件を使用してawaitメソッドを呼び出してラウンジに入り、待機することができます。目が覚めたら、それは合図です( )メソッド
2. AQSの原理
1. AQS の概要
Abstract Queued Synchronizer、Abstract Queue Synchronizer は、ブロッキングロックおよび関連するシンクロナイザー ツール フレームワークであり、主にその機能を継承し、その機能を再利用します。
特徴:
- state 属性はリソースの状態(排他モードと共有モデルに分けられる) を表すために使用され、サブクラスはこの状態を維持する方法を定義し、ロックの取得と解放の方法を制御する必要があります。
- getState 状態を取得する
- setState 状態を設定します
- CompareAndSetState - 状態 state を設定する cas メカニズム (cas は、スレッドセーフである場合、複数のスレッドが状態を変更するのを防ぎます)
- 排他モードでは 1 つのスレッドのみがリソースにアクセスできますが、共有モードでは複数のスレッドがリソースにアクセスできます。
- モニターの EntryList と同様の、FIFO ベースの待機キューを提供します。
- 条件変数は待機およびウェイクアップのメカニズムを実装するために使用され、モニターの waitSet と同様に複数の条件変数がサポートされています。
ロックの取得: tryAcquire(arg)はブール値を返し、パークとパーク解除は aqs での一時停止と再開に使用されます。
ロックを解放します: tryRelease(arg)はブール値を返します
2.カスタムロック
カスタム ロック メソッドは基本的に AQS を通じて実装されます。
- tryAcquire で状態を変更します。今回は排他ロックを使用し、AQS オブジェクト ロックの所有者を設定します。
- 次に、tryRelease があります。これは主に状態を 0 に設定し、ロックを解除し、所有者を解放します。
- isHeldExcusively: オブジェクトがロックを占有しているかどうかを判断します。
- newCondition は、実際には AQS の ConditionObject オブジェクトであり、条件変数を作成します。
最後に、ロックを実装する方法は、基本的には間接的にシンクロナイザーを呼び出して実行する方法です。
class MyLock implements Lock {
//先实现同步器,实现AQS抽象类,他已经帮我们写了大多数方法,我们只需要自定义4个方法
class MySync extends AbstractQueuedSynchronizer{
@Override
protected boolean tryAcquire(int arg) {
//尝试加锁
if(compareAndSetState(0,1)){
//这个用的是CAS的无锁机制加锁防止多线程安全问题
setExclusiveOwnerThread(Thread.currentThread());
return true;
}
return false;
}
@Override
protected boolean tryRelease(int arg) {
//尝试释放锁
setExclusiveOwnerThread(null);
//这个state是volatile修饰,防止指令重排
setState(0);
return true;
}
@Override
protected boolean isHeldExclusively() {
//锁是不是被线程持有
return getState()==1;
}
public Condition newCondition(){
return new ConditionObject();
}
}
private MySync sync=new MySync();
@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 {
long waitTime = unit.toNanos(time);
return sync.tryAcquireNanos(1,waitTime);
}
@Override
public void unlock() {
//他这个释放锁,调用的是release方法,这个方法还会唤醒堵塞队列中的一个线程
sync.release(1);
}
@Override
public Condition newCondition() {
// sync.newCondition();
return sync.newCondition();
}
}
三、ReentrantLock実装原理
また、AQS を継承する抽象 Sync クラスが下部にあり、NonfairSync 不公平ロックとFairSync 公正ロックの 2 つの実装があります。
1. 不正ロックの実装
ロック処理
まず、cas の CompareAndSetState を使用してロックを試みます。競合がない場合、ロックが成功すると、setExclusiveOwnerThread が現在のスレッドに変更されます。
最初の競合が発生すると、自分自身で tryAcquire を再試行し、成功するとロックが成功します。
それでもロックが失敗する場合は、ノード キューが構築され、ヘッドはダミーまたはセンチネルと呼ばれる最初のノードを指します。このノードは場所を占め、スレッドには関連付けられません。
次に、無限ループのサマリーに戻ってロックの取得を試行し続け、失敗したらパーク ブロックに入ります。
先頭から 2 番目の場合は、もう一度 tryAcquire を実行してロックの取得を試みることができますが、状態が 1 のままの場合は失敗します。
フロント ノード、つまりヘッドの waitStatus を -1 に変更します (ブロックされようとしていて、ヘッドがステータス 1 であることを示しているため、後でヘッドをウェイクアップする必要があります)。今回は false を返します。
このとき、再度ループし、前駆ノードが -1 であるため、entry メソッドは true を返し、その後 park メソッドが呼び出されて現在のスレッドをブロックします。
複数のノードがパークおよびブロックされている場合、ノードは 1 つずつ入力され、各状態の上にある小さな三角形は -1 になり、プリカーサー ノードがそのノードをウェイクアップする責任があることを示します。
ロック解除処理
tryRelease を実行します。成功した場合は、exclusiveOwnerThread を null に直接設定し、状態を 0 に設定します。
現在のキューが null でなく、ヘッドの waitStatus が -1 の場合、キュー内でヘッドに最も近いノードが検索され、パーク解除はその操作を再開し、上記のロック メソッドを実行します。
しかし、これは不公平なロックです。このとき、キューにないスレッド 4 が突然来ると、先ほど目覚めたスレッドと競合します。4 が先にロックを取得し、それを exclusiveOwnerThread に設定すると、1 がロックを取得します。再駐車してブロックに入る
2. リエントラントの原則
不公平なロックを例に挙げると、ロックを取得すると、nonfairTryAcquire が呼び出されます。
まず状態を判断し、0の場合はロックがないことを意味し、直接ロックします。
0でない場合はロック中であることを示し、現在のスレッドがexclusiveOwnerThreadのスレッドと同じかどうかを判定し、同じであればリエントラントであることを示します。
解放されるとtryReleaseメソッドが呼び出され、呼び出し後にstateが呼び出され、0になったらロックが解除され、exclusiveOwnerThreadをnullにしてstateを0にする
3. 中断可能な原理
無中断モード
デフォルトはnon-interruptibleです。スレッドが変更せずにすぐにロックを取得すると、ループ内で継続的にロックの取得を試行します。それでも失敗する場合は、パークに入ります。パークに入ると、他のスレッドによって起動される可能性があります。彼を中断するスレッドがあります。割り込みフラグのデフォルト値は false です。中断されると、色は true に設定されますが、彼はフラグを変更しただけで、サイクル パークに再入場しました。ウェイクアップしますが、ロックを取得しないか、ブロックを続けますが、true フラグがあります
非割り込みモードでは、割り込みが発生した場合でも、スレッドは AQS キューに常駐し、ロックを取得した後も実行を継続します。ロックを取得した後でのみ、他のスレッドが割り込みを行ったことが分かります (割り込みフラグが設定されています)。本当です)
割り込み可能モード
doAcquireInterruptibly が呼び出されると、ロックを取得することも行われ、キューにパークが入ります。このとき、他のスレッドが起動して実行を継続し、直接 InterruptedException をスローするため、ループで待つ必要はありません。中断される可能性があります。
4. フェアロック原則
不公平なロックは、ロックを取得するための非公平なTryAcquireです。ロックを取得するとき、状態が 0 の場合、誰もロックを取得していないことを意味し、判断せずに直接ロックを取得します。
公平なロックの判定条件は複数あり、ブロックされたキューにスレッドが残っている場合はロックを取得しません。
5. 条件変数の原理
プロセスを待つ
- 1 つ目は、スレッドを対応する待機条件キュー (ラウンジに相当) に入れることです。違いは、ノードの前の null ノードがなくなり、待機状態が -2 になったことです。
- これは完全にリリースを実行し、直接 0 に変更します。これは、再入や他のロックの取得を防ぐためにロックをクリアします。次に、同期キューのスレッドを起動します。通常のリリースが -1 のみの場合、リエントラント ロックは待機ルームに入ったにもかかわらず、まだ exclusiveOwnerThread 内にあります。
- 次に、ブロック条件を入力します
概要: 条件キューに入り、ロックをクリアしてスレッドをウェイクアップし、最後にパークを使用してブロックします。
信号処理
- まずスレッドがロックを取得しているか確認し、ウェイクアップする権利は保持者のみにあり、保持者でない場合は例外をスローし、キューの先頭ノードを取得します。
- 次に、 doSignal を呼び出して最初に起動し、同期キューに参加します (同期に参加した後でのみ、オーナー コンペティション ロックの実行に入ることができます)。待ってからラウンジに入るのと同様に、目覚めた後もキュー コンペティションに参加する必要があります。
- 次のステップでは、次のノード (つまり、実際の条件付きキュー ノード) を取得します。それが null の場合、最後のノードは空に設定されます。
- 空でない場合は、ノードが削除され、次のノードが空に設定されます (firstWaiter に保存されます)。
- if ( (firstWaiter = first.nextWaiter) == null)
- lastWaiter = null;
- first.nextWaiter = null;
- まず第一に、これは一方向のリンク リストであり、チームの先頭を指すノードとチームの最後を指すノードがあることを知っておく必要があります。以下の addConditionWaiter メソッドを見ることができます。チームの最後にある次のノードが新しいノードを直接指すようになり、チームの最後 = 新しいノードになります。そういう動き。ここでの first.nextWaiter は最初に接続を切断するだけであり、firstWaiter は null には設定されません。ポインタが次のノードを指していないだけです。それぞれの時間は、firstWaiter チーム リーダーを後ろに移動し、最初のノードを同期キューに入れて待機させることと同じです。
- 次に、transferForSignal メソッドを通じて最初のノードを転送し、ノードの状態を 0 に設定します。
- Node p = enq(node); 最後に、ノードは同期キューに結合され、前のノードに戻されます。
- 先行ノードのステータスを -1 に変更します。信号ウェイクアップを終了しました
概要: Signal は条件付きキューのクリア (単一アイテムのリンク リストのクリア) を完了し、対応するすべてのノードを同期キューに送信します。失敗した場合は、キューがいっぱいであるか、タイムアウトになっている可能性があります。最後のステップは、先行ノードを取り出して状態を変更することです。