データを同時に共有し、一貫性を確保するためのツールとして、ロックは JAVA プラットフォーム上に複数の実装があります (synchronized ロックや ReentrantLock など)。これらの既に書き込まれたロックは開発に便利ですが、ロックの具体的な性質や種類についてはほとんど言及されません。このシリーズの記事では、JAVA での一般的なロック名と特徴を分析して、質問に答えます。
1. スピンロック
スピン ロックは、現在のスレッドがループ本体内で継続的に実行できるようにすることで実装されます。ループの条件が他のスレッドによって変更された場合にのみ、クリティカル セクションに入ることができます。次のようにコードをコピーします。
パブリック クラス SpinLock {
private AtomicReference<Thread>sign =new AtomicReference<>();
パブリック void ロック(){
スレッド電流 = Thread.currentThread();
while(!sign .compareAndSet(null, current)){
}
}
public void ロック解除 (){
スレッド電流 = Thread.currentThread();
符号 .compareAndSet(current, null);
}
}
CAS アトミック操作を使用して、ロック関数は所有者を現在のスレッドに設定し、元の値が空であると予測します。ロック解除関数は所有者を null に設定し、予測値は現在のスレッドになります。
2 番目のスレッドがロック操作を呼び出すと、所有者の値が空ではないため、最初のスレッドがロック解除関数を呼び出して所有者を null に設定するまでループが実行され、2 番目のスレッドはクリティカル セクションに入ることができます。
スピンロックは、スレッドの状態を変更せずに、現在のスレッドでループ本体の実行を維持するだけであるため、応答速度が速くなります。ただし、スレッド数が増加し続けると、各スレッドを実行する必要があり CPU 時間を占有するため、パフォーマンスが大幅に低下します。スレッドの競合が激しくなく、ロックが一定期間維持される場合。スピンロックとの使用に適しています。
注: この例は不公平なロックです。ロックを取得する順序は、ロックに入る順序に基づきません。
2. 他のタイプのスピンロック
スピン ロックについては上で説明しました。スピン ロックには、TicketLock、CLHlock、MCSlock という 3 つの一般的なロック形式があります。
チケット ロックは主にアクセス シーケンスの問題を解決します。主な問題はマルチコア CPU 上にあります。
次のようにコードをコピーします。
パッケージcom.alipay.titan.dcc.dal.entity;
インポート java.util.concurrent.atomic.AtomicInteger;
パブリック クラス TicketLock {
private AtomicInteger serviceNum = new AtomicInteger();
private AtomicInteger ticketNum = new AtomicInteger();
private static Final ThreadLocal<Integer> LOCAL = new ThreadLocal<Integer>();
public void lock() {
int myticket = ticketNum.getAndIncrement();
LOCAL.set(myticket);
while (myticket != serviceNum.get()) {
}
}
パブリック void ロック解除() {
int myticket = LOCAL.get();
serviceNum.compareAndSet(myticket, myticket + 1);
}
}
serviceNum サービス番号は毎回クエリする必要があり、パフォーマンスに影響します (サービス番号はメイン メモリから読み取られる必要があり、他の CPU が変更できないようにする必要があります)。
CLHLock と MCSLock は、リンク リストの形式でソートされた 2 つの類似したタイプのフェア ロックです。
次のようにコードをコピーします。
インポート java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
パブリック クラス CLHlock {
パブリック静的クラス CLHNode {
プライベート揮発性ブール値 isLocked = true;
}
@SuppressWarnings("未使用")
プライベートの揮発性 CLHNode テール。
private static Final ThreadLocal<CLHNode> LOCAL = new ThreadLocal<CLHNode>();
private static Final AtomicReferenceFieldUpdater<CLHLock, CLHNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(CLHLock.class,
CLHNode.class、「テール」);
public void lock() {
CLHNode ノード = 新しい CLHNode();
LOCAL.set(ノード);
CLHNode preNode = UPDATER.getAndSet(this, ノード);
if (preNode != null) {
while (preNode.isLocked) {
}
preNode = null;
LOCAL.set(ノード);
}
}
パブリック void ロック解除() {
CLHNode ノード = LOCAL.get();
if (!UPDATER.compareAndSet(this, ノード, null)) {
ノード.isLocked = false;
}
ノード = null;
}
}
CLHlock はプリカーサー変数を継続的にクエリするため、NUMA アーキテクチャでの使用には適していません (このアーキテクチャでは、各スレッドは異なる物理メモリ領域に分散されます)。
MCSLock は、ローカル変数のノードをループします。 CLHlockは問題ありません。
次のようにコードをコピーします。
インポート java.util.concurrent.atomic.AtomicReferenceFieldUpdater;
パブリック クラス MCSlock {
パブリック静的クラス MCSNode {
次に揮発性 MCSNode。
揮発性ブール値 isLocked = true;
}
プライベート静的最終 ThreadLocal<MCSNode> NODE = new ThreadLocal<MCSNode>();
@SuppressWarnings("未使用")
プライベートの揮発性 MCSNode キュー。
private static Final AtomicReferenceFieldUpdater<MCSLock, MCSNode> UPDATER = AtomicReferenceFieldUpdater.newUpdater(MCSLock.class,
MCSNode.class、「キュー」);
public void lock() {
MCSNode currentNode = 新しい MCSNode();
NODE.set(現在のノード);
MCSNode preNode = UPDATER.getAndSet(this, currentNode);
if (preNode != null) {
preNode.next = 現在のノード;
while (currentNode.isLocked) {
}
}
}
パブリック void ロック解除() {
MCSNode currentNode = NODE.get();
if (currentNode.next == null) {
if (UPDATER.compareAndSet(this, currentNode, null)) {
} それ以外 {
while (currentNode.next == null) {
}
}
} それ以外 {
currentNode.next.isLocked = false;
currentNode.next = null;
}
}
}
コードの観点から見ると、CLH は MCS よりも単純です。
CLH キューは暗黙的なキューであり、実際の後続ノード属性はありません。
MCS キューは、実際の後続ノード属性を持つ明示的なキューです。
JUC ReentrantLock によって内部的に使用されるデフォルトのロックは CLH ロックです (スピン ロックをブロッキング ロックに置き換えるなど、多くの改良が加えられています)。
(全文終わり)