前に書く
AQSを学習するセマフォソースコード
デモとソースコードの学習
セマフォセマフォは現在の制限であり、セマフォを取得するスレッドのみが実行を許可されるたびに
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(2);
for (int i = 1; i <= 5; i++) {
new Thread(()->{
try {
semaphore.acquire();
System.out.println(Thread.currentThread().getName()+":aquire 时间:"+System.currentTimeMillis());
Thread.sleep(1000);
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
},"线程" + i).start();
}
}
結果
线程1:aquire 时间:1612068740726
线程2:aquire 时间:1612068740726
线程3:aquire 时间:1612068741727
线程4:aquire 时间:1612068741727
线程5:aquire 时间:1612068742728
5つのスレッドを開きましたが、毎回2つのスレッドのみが実行されます。セマフォが2つしかないため、セマフォを取得したスレッドのみが実行されます。現在の2つのスレッドがすべてのセマフォを取得した後、セマフォは0であり、別のスレッドは、他のスレッドがセマフォを解放するのを待って、入ってくるとブロックします。
ソースコードに基づいた少しの分析
semaphore.acquire();
セマフォメソッドを取得する
public Semaphore(int permits) {
//信号量构造方法 默认创建非公平锁
sync = new NonfairSync(permits);
}
sync.acquireSharedInterruptibly(1);
public final void acquireSharedInterruptibly(int arg)
throws InterruptedException {
//线程是否中断 如果线程中断过抛出异常
if (Thread.interrupted())
throw new InterruptedException();
//尝试获取共享锁,此处aqs与前面有些不同,前面是独占锁
//这里记住是共享锁,因为会有多个线程来修改state值
if (tryAcquireShared(arg) < 0)
//获取共享锁,获取信号量
doAcquireSharedInterruptibly(arg);
}
tryAcquireShared
最初に不公平なロックの実装を見て、最後に次のメソッドを呼び出しましょう
final int nonfairTryAcquireShared(int acquires) {
//死循环
for (;;) {
//获取信号量的状态值,按照demo例子,此处为2
int available = getState();
//acquires 为 1 , 可用的状态值 - 1
int remaining = available - acquires;
//如果值小于0,返回小于0的值
//如果大于0,设置状态值成功了,返回状态值
if (remaining < 0 ||
compareAndSetState(available, remaining))
return remaining;
}
}
- Loop plus casは同じルーチンであり、マルチスレッドでこのメソッドを正しく実行するために使用されます
- 状態値を取得します。開始値は2です。この時点でこのメソッドに4つのスレッド(ABCD)が一緒に入力されている場合、残りの値は1ですが、compareAndSetStateを実行する1つのスレッドのみが正常に実行されます。このAスレッドが成功すると仮定すると、これを返す1
- 残りのスレッドはループを続けます。このとき、2回目に実行されるスレッド(BCD)は3つあります。このとき、残りは0に等しく、すべて1-1であり、残りは少なくありません。この時点で、compareAndSetStateメソッドの実行を続行すると、現時点では1つだけになります。このBスレッドcasが成功したと仮定すると、スレッドは正常に実行され、状態値は1から0に設定され、0が返されます。
- 残りの2つのスレッド(CD)は3番目のサイクルに入りますが、この時点では状態は0であり、1を引いた後の値が-1の場合、直接-1を返し、両方のスレッドが-1を返します。
if (tryAcquireShared(arg) < 0)
doAcquireSharedInterruptibly(arg);
- 上記のコードを見てください。上記の例によると、最初の2つのスレッドの1つは1を返し、もう1つは0を返します。ifに移動する代わりに、直接戻ってビジネスコードを実行します。
- CDスレッドはすべて-1を返し、elseメソッドを入力します
doAcquireSharedInterruptibly
private void doAcquireSharedInterruptibly(int arg)
throws InterruptedException {
//将当前线程加入到CLH队列,加入到队尾
final Node node = addWaiter(Node.SHARED);
boolean failed = true;
try {
for (;;) {
//获取节点的前置节点是否是头节点,之后head节点的下一个节点才有机会获取到锁,其余节点都需要进行排队等待
final Node p = node.predecessor();
if (p == head) {
int r = tryAcquireShared(arg);
if (r >= 0) {
setHeadAndPropagate(node, r);
p.next = null; // help GC
failed = false;
return;
}
}
//获取当前节点的前置节点,将其状态设置为-1,因为前置节点为-1时才会唤醒后续节点
//阻塞住当前线程
if (shouldParkAfterFailedAcquire(p, node) &&
parkAndCheckInterrupt())
throw new InterruptedException();
}
} finally {
if (failed)
cancelAcquire(node);
}
}
- メインロジックはコードでコメントされています
- これは、再入可能ロックのロジックに似ています。違いは、共有ロックを取得しようとするときです。
- コードの詳細については説明しません。前述の再入力ロックとほぼ同じです。不整合は共有ロックです。共有ロックのサブクラス実装を見てみましょう。
- 一般的なロジックは、上記の例によれば、この時点でCDスレッドがこのメソッドに入り、最初にCDスレッドをキューに追加し、属性を共有するように設定します。
- CDスレッドは一緒になりますが、キュー
内の現在の状況がhead-> C-> Dの場合、キューにはまだ順序があります - ここで、CDは一緒に無限ループに入り、最初にCDノードのフロントノードがヘッドノードであるかどうかを判断します。明らかに、Cは、Dはそうではなく、Dは次のメソッドに直接移動してスレッドとブロックに入り、ウェイクアップを待機します。
- Cスレッドは、最初にセマフォを取得しようとし、スレッドがセマフォを解放するかどうかを確認し、スレッドがセマフォを解放する場合は、セマフォを取得して直接戻り、ビジネスコードに移動します。
- 現時点では、まだ公平性と不公平性があります。Cが実行してロックコード(tryAcquireShared)を取得しようとすると、この時点でスレッドEが入ったばかりで、セマフォが0ではないことが起こります。時間、EスレッドとキューがありますCスレッドはこのセマフォを競い合い、競争に成功した人は誰でも実行のためにセマフォを取得します。公平であれば、EはCでセマフォをプリエンプトせず、Eは最後に進みます素直にキューの。それは頭です-> C-> D-> E
主に不公平なロックに注目します。これは、共有ロックを取得するために前述したコードです。
semaphore.release()
セマフォ方式を解除する
public void release() {
sync.releaseShared(1);
}
//释放信号量
public final boolean releaseShared(int arg) {
//尝试释放信号量
if (tryReleaseShared(arg)) {
//执行唤醒
doReleaseShared();
return true;
}
return false;
}
tryReleaseShared
protected final boolean tryReleaseShared(int releases) {
for (;;) {
int current = getState();
int next = current + releases;
if (next < current) // overflow
throw new Error("Maximum permit count exceeded");
if (compareAndSetState(current, next))
return true;
}
}
- この時点で、上記の例に従って説明すると、ABスレッドはロックを解放しようとします
- 無限ループとaqsまたはマルチスレッドの状況でコードを正しく実行できます
- ABは同時に入り、状態0を取得し、1つの操作を追加してから、casを使用して状態を1に設定します。1つのcasのみが成功し、成功後にtrueを返します。
- 別のスレッドが次のサイクルを実行し、状態を2に設定します
状態が0より大きい場合、セマフォがあります。次のコードを実行します
private void doReleaseShared() {
for (;;) {
//获取头节点
Node h = head;
//说明队列不为空
if (h != null && h != tail) {
//获取状态
int ws = h.waitStatus;
//状态等于-1说明需要唤醒后续节点
if (ws == Node.SIGNAL) {
//将当前状态置为0
if (!compareAndSetWaitStatus(h, Node.SIGNAL, 0))
continue; // loop to recheck cases
//唤醒head的下一个节点,同重入锁代码
unparkSuccessor(h);
}
else if (ws == 0 &&
!compareAndSetWaitStatus(h, 0, Node.PROPAGATE))
continue; // loop on failed CAS
}
//如果head变了,说明已经有新的线程获取到信号量了,继续循环
if (h == head) // loop if head changed
break;
}
}
- このコードのロジックは、キューのスレッドをウェイクアップすることです
- 例によると、Aスレッドはセマフォを解放し、releaseメソッドを実行してから、上記のメソッドを介してキュー内のスレッドをウェイクアップします。
- ヘッドが変更されていない場合は、キュー内のスレッドが現在のメソッドよりも遅く実行されていることを意味します。ヘッドが変更されている場合は、セマフォを取得したスレッドがキュー内にあることを意味します。
ウェイクアップコード
- ループはいつ終了しますか?Aスレッドがセマフォを解放してキュー内のCスレッドをウェイクアップすると、Cスレッドはヘッドの次のノードであるため、Cスレッドはブロックされてウェイクアップされ、次のループが実行されます。ロックの取得に成功します。このとき、Cスレッドが取得します。セマフォにヘッドが変わります。
- 頭が変わった後もループを続ける
- Dスレッドはヘッドの次のノードであるため、Dをウェイクアップし続けると、Dスレッドがブロックされてウェイクアップされ、次のサイクルが実行されますが、ロックの取得の試みは失敗し、Dスレッドはブロックを継続します。頭は変わりません。
- 頭は変わっていません、次のループは終わります
上記はセマフォの一般的なソースコード分析です。間違いがあれば、批判や訂正を歓迎します。