同期ロックおよび ReentrantLock ロックの実装

同期ロックの実装

Synchronized は、ロックを自動的に取得/解放するようにコード ブロックとメソッドを変更するキーワードです。

実装方法: javaの各オブジェクトを監視ロックと見なす. スレッドコードが同期コードブロックに入ると, 自動的にロックを獲得する. このとき, 他のスレッドは現在のスレッドが実行されるまでブロックする. または例外、待機などはロックを解放します。これは、メソッドの同期とコード ブロックの同期を実現するためにモニターに出入りすることに基づいています。

同期メソッドは、ACC_SYNCHRONIZED を使用して、それが同期メソッドであるかどうかをマークします。メソッドが呼び出されると、call 命令は、メソッドの ACC_SYNCHRONIZED アクセス フラグが設定されているかどうかをチェックします. 設定されている場合、フラグは、スレッドがメソッドに入るときに monitorenter が必要であり、メソッドを終了するときに、それが必要であることを示します. monitorexit が必要です。

コード ブロックの同期には、monitorenter と monitorexit という 2 つのバイトコード命令が使用されます。

仮想マシンが monitorenter 命令を実行すると、まずオブジェクトのロックを取得しようとします。

現在のスレッドはこのオブジェクトのロックを所有しており、ロックのカウンターは +1 です; monitorexit 命令が実行されると、モジュロ カウンターは -1 です; カウンターが 0 の場合、ロックは解放され、Java での同期はオブジェクトヘッダーにマークを設定することで実現 ロックの取得と解放を目的とする。

ReentrantLock ロックの実装:

ReentrantLock は AQS (AbstractQueuedSynchronizer) の考え方に基づいて実装されているため、ここでは AQS の考え方について簡単に説明します。

AQS の実装原則

ロックが使用されているかどうかを示す状態変数が内部にあり、0 に初期化されますスレッドが正常に取得すると、状態が 1 増加し、他のスレッドが再度取得すると、共有リソースが占有されているため、FIFO (先入れ先出し)キューに移動して待機します。状態がクリティカル セクション内のコードの実行を終了してリソースを解放する (状態マイナス 1)、それはウェイクアップします。

FIFO 内の次の待機スレッド (ヘッド内の次のノード) が状態を取得します。状態はマルチスレッド共有変数であるため、状態の可視性を保証するために volatile として定義する必要があります. 同時に、揮発性は可視性を保証できますが、原子性を保証することはできません. したがって, AQS は状態の原子操作メソッドを提供します.スレッドの安全性を確保するため。

また、AQSで実装されているFIFOキューは、実際には二重連結リストで実装されており、ヘッドノードは現在占有されているスレッドを表し、他のノードは一時的にロックを取得できないため、ロックが解放されるのを待ちます。

キューは Node オブジェクトで構成され、Node は AQS の内部クラスです。

AbstractQueuedSynchronizer メンバー

private transient volatile Node head;
private transient volatile Node tail;
/*
使用变量 state 表示锁状态,0-锁未被使用,大于 0 锁已被使用
共享变量 state,使用 volatile 修饰保证线程可见性
*/
private volatile int state;

状態情報は、 getState 、 setState 、 compareAndSetState によって操作されます。

protected final int getState() { //获得锁状态
return state;
}
protected final void setState(int newState) {//设置锁状态
state = newState;
}
//使用 CAS 机制设置状态
protected final boolean compareAndSetState(int expect, int update) {
return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

AQS のいくつかの操作方法:

acquire: ロックを取得する必要があることを示します

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

tryAcquire: ロックの取得を試みます。tryAcquire がロックの取得に成功した場合は、次のようになります。tryAcquire(arg) は false であり、ロックが取得されていることを示し、キューイングにまったく参加する必要がない、つまり後続の判定条件を実行する必要がないことを示します。判定条件のショートルールに従い、直帰します。

addWaiter: ロックの取得に失敗した後、現在のスレッドを Node オブジェクトにカプセル化し、それをキューの最後に追加して、Node ノードを返します。

acquireQueued: キューにスレッドを追加した後、スピン モードでロックを取得します。

リリース リリースロック

tryRelease: ロックを解除し、状態値を 0 に変更します

unparkSuccessor: ノードのサクセサーをパーク解除します (存在する場合)

AQS のロック モードは、排他的および共有に分けられます。

排他的ロック: 一度に 1 つのスレッドのみがロックを保持できます.たとえば、ReentrantLock は排他的に実装されたミューテックスです.

共有ロック: 複数のスレッドが同時にロックを取得し、共有リソースに同時にアクセスできるようにします。ReentrantReadWriteLock よりも優れています。

以上がAQSの実装案です

ReentrantLockは AQS に基づいています. 並行プログラミングでは、共有リソースを同期するために公平なロックと不公平なロックを実装できます. 同時に、同期されたように、ReentrantLock は再入可能性をサポートします. さらに、ReentrantLock はスケジューリングでより柔軟で、より多くの豊富な機能をサポートします.

ReentrantLock には合計 3 つの内部クラスがあり、3 つの内部クラスは密接に関連しています。

ReentrantLock クラス内には Sync、NonfairSync、FairSync の合計 3 つのクラスがあり、NonfairSync と

FairSync クラスは Sync クラスを継承し、Sync クラスは AbstractQueuedSynchronizer 抽象クラスを継承します。

ReentrantLock 構築方法:

Sync クラスは AbstractQueuedSynchronizer を継承します

abstract static class Sync extends AbstractQueuedSynchronizer

NonfairSync クラスは Sync クラスを継承し、不当な戦略を使用してロックが取得されたことを示し、Sync クラスに抽象ロック メソッドを実装します。

static final class NonfairSync extends Sync {
//加锁
final void lock() {
//若通过 CAS 设置变量 state 成功,就是获取锁成功,则将当前线程设置为独占线程。
//若通过 CAS 设置变量 state 失败,就是获取锁失败,则进入 acquire 方法进行后续处理。
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
//尝试获取锁,无论是否获得都立即返回
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
}

FairSync クラスも Sync クラスを継承しています。つまり、公正な戦略を使用してロックを取得し、Sync クラスで抽象ロック メソッドを実装します。

static final class FairSync extends Sync {
final void lock() {
// 以独占模式获取对象,忽略中断
acquire(1);//底层实现交由 AbstractQueuedSynchronizer
}
}

おすすめ

転載: blog.csdn.net/weixin_71243923/article/details/128924111