ハードコア Java テクノロジーを共有する金漁師のWang Youzhiに注目してください。Java人々のグループ
へようこそ:一緒に裕福な Java 人々
今日は、AQS ファミリのもう 1 つの重要なメンバーである CountDownLatch について説明します。CountDownLatch に関する面接の質問はそれほど多くありませんが、「何を」「どのように実装するか」という質問に加えて、CountDownLatch は CyclicBarrier と比較されます。
-
カウントダウンラッチとは何ですか? それはどのように達成されるのでしょうか?
-
CountDownLatch と CyclicBarrier の違いは何ですか?
いつも通り、CountDownLatch を「何を」「どう使う」「どのように実装する」の 3 つのステップで分析しますが、CyclicBarrier との違いについては、次の記事で詳しく分析します。
Tips : 今日の「何を」と「どう使うか」を融合しました。
CountDownLatch の使用
あなたは、上司を動かし、組織の結束力を「高める」というテーマを伴う会社のチームビルディングに参加したことがありますか? 通常、行政はクロスカントリーハイキング活動を企画し、全員がゴールに到着した後にのみ食事ができると規定するが、これは「諦めず諦めないチーム精神」と呼ばれる。そして、上司は名簿を持って早めにゴールラインで待ち、従業員がゴールラインに到着したら、名簿上の自分の名前を消します。
このようなクロスカントリー ハイキング アクティビティでは、簡単なコードの記述に CountDownLatch を使用できます。
CountDownLatch countDownLatch = new CountDownLatch(10);
// 10个人进行越野徒步
for (int i = 0; i < 10; i++) {
int finalI = i;
new Thread(() -> {
try {
// 每个人比前一个选手晚1秒
TimeUnit.SECONDS.sleep((finalI + 1));
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
System.out.println("选手[" + finalI + "]到达终点!!!");
countDownLatch.countDown();
}).start();
}
// 老板在目的地吃瓜,等待每个选手到达
countDownLatch.await();
// 开饭啦!
System.out.println("老板说:所有人都到齐了,午饭是每人一个吐司!!!");
これを見て、このようなグループビルディング活動に参加した友達は高血圧ですか?ただし、高くなりすぎないでください。血圧が急上昇するこのようなチーム構築では、私たちは無意識のうちに CountDownLatch の使用法を習得しているからです。
まずは CountDownLatch を名前から理解してみましょう。CountDownLatch は合成語です。CountDown は「カウントダウン」、Latch は「ドアラッチ」と訳されます。この組み合わせは、カウントダウン後に (次のアクションのために) ラッチを開くことを意味します。Doug Lea が CountDownLatch の役割をどのように説明しているかを見てみましょう。
他のスレッドで実行されている一連の操作が完了するまで 1 つ以上のスレッドを待機できるようにする同期補助。
CountDownLatch は、1 つまたは複数のスレッドが他のスレッドが操作を完了する (つまり、後続の操作を実行する) のを待機できるようにする同期補助機能です。
CountDownLatch.await
CountDownLatch では 1 つ以上のスレッドを待機させることができ、複数のスレッドの待機を実現するには、別のスレッドでこれを呼び出すだけでよいことに注意してください。
CountDownLatch の原理
まず、CountDownLatch が AQS ファミリのメンバーとして AQS にどのように関連しているかを見てみましょう。ReentrantLock
や Semaphore などのよく知られた構造は、AQS を継承する内部シンクロナイザー クラスですSync
が、異なる点は、CountDownLatch がSync
抽象クラスではなくなっていることです。AQS から継承されており、内部カウンタ (カウントダウンもカウント) があるため、同期状態の説明を「 AQS の現在、JUC の基盤の構築
」のカウンタ機能として再度削除します。 :
AQS では、状態は同期状態を表すために使用されるだけでなく、一部のシンクロナイザーによって実装されるカウンターも使用されます。たとえば、
Semaphore
AQS でのパススルーが許可されるスレッドの数、ReentrantLock
メディア内のリエントラント機能の実現などはすべて依存します。state
カウンターの特性について。
CountDownLatch の例はありませんが、セマフォの分析後は、CountDownLatch がカウンター機能として同期状態をどのように使用するかを推測できるはずです。次に、CountDownLatch での同期状態の適用を見てみましょう。
施工方法
AQS ファミリのメンバーのクラス図からわかるように、CountDownLatch のシンクロナイザーにはSync
公平性と不公平性の区別がないため、コンストラクターはカウントを設定する機能のみを提供する必要があります。
public class CountDownLatch {
public CountDownLatch(int count) {
if (count < 0){
throw new IllegalArgumentException("count < 0");
}
this.sync = new Sync(count);
}
private static final class Sync extends AbstractQueuedSynchronizer {
Sync(int count) {
setState(count);
}
}
}
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
protected final void setState(int newState) {
state = newState;
}
}
予想どおり、 CountDownLatch のカウントは AQS に返されますstate
。
countDown方法
ハイキング活動の話に戻りますが、従業員がゴールした後、名簿に名前を記入し、最後の人が到着すると鉦と太鼓が鳴らされます。コードの実装では、CountDownLatch.countDown
従業員の到着を示す状態を使用し、対応するアクションを実行します。
public class CountDownLatch {
public void countDown() {
sync.releaseShared(1);
}
private static final class Sync extends AbstractQueuedSynchronizer {
protected boolean tryReleaseShared(int releases) {
for (;;) {
// 获取同步状态
int c = getState();
// 同步状态为0,返回失败
if (c == 0){
return false;
}
// 计数减1,并通过CAS更新
int nextc = c - 1;
if (compareAndSetState(c, nextc)) {
// 计数器为0时返回true
return nextc == 0;
}
}
}
}
}
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
public final boolean releaseShared(int arg) {
if (tryReleaseShared(arg)) {
doReleaseShared();
return true;
}
return false;
}
}
「 AQS ファミリのメンバーの詳細な説明: セマフォ」のメソッドの実装を思い出してくださいSemaphore#release
。見覚えがあるでしょうか? Sync#tryReleaseShared
メソッドを実行し、成功後に AQS メソッドを呼び出すのも同様ですdoReleaseShared
。違いはSemaphore#tryReleaseShared
、実装ではカウントに 1 を加算するのに対し、CountDownLatch#tryReleaseShared
実装ではカウントから 1 を減算することです。
もう一つの問題点に注目すると、CountDownLatchメソッドはカウンタが 0 になった場合Sync#tryReleaseShared
にのみtrue を返し、この時点で AQSdoReleaseShared
メソッドに入ることができますが、それ以外の場合はカウンタを 1 減らす操作を実行するだけです。
さらに、AQSdoReleaseShared
メソッドが AQS 待機キュー内のノードをウェイクアップする役割を果たすこともわかっています。つまり、カウンターが 0 に減った場合にのみ、CountDownLatch がウェイクアップ作業を実行します。
ヒント: AQS については、doReleaseShared
「AQS ファミリーのメンバーの詳細な説明: セマフォ」で分析されているので、繰り返しません~~
await メソッド
上司が早朝に車で目的地に到着して待っていたことはわかりますが、上司は待たなければならないとどのように判断するのでしょうか。上司は予定より早くゴールに到着した後、名簿を取り出して到着者数を数え、まだゴールに到着していない人がいることを確認すると、仮眠を取る準備をし、寝る。
CountDownLatch.await
以前は、ボスが待機状態に入ったことを示していました。
public class CountDownLatch {
public void await() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
}
public abstract class AbstractQueuedSynchronizer extends AbstractOwnableSynchronizer implements java.io.Serializable {
public final void acquireSharedInterruptibly(int arg) throws InterruptedException {
if (Thread.interrupted()) {
throw new InterruptedException();
}
if (tryAcquireShared(arg) < 0) {
doAcquireSharedInterruptibly(arg);
}
}
}
まだおなじみですか?AQS メソッドは Semaphore と同様に使用されるacquireSharedInterruptibly
ため、CountDownLatchSync#tryAcquireShared
メソッドに焦点を当てます。
public class CountDownLatch {
private static final class Sync extends AbstractQueuedSynchronizer {
protected int tryAcquireShared(int acquires) {
// 同步状态为0返回1,不为0返回-1
return (getState() == 0) ? 1 : -1;
}
}
}
この方法は同期状態を判断し、AQSacquireSharedInterruptibly
方法と組み合わせると、次の結論が得られます。
-
同期状態が 0 の場合、
tryAcquireShared
1 を返し、実行しません。doAcquireSharedInterruptibly
つまり、十分な回数実行するとcountDownLatch#countDown
、待機キューに入る必要はありません。 -
同期状態が 0 に等しくない場合は、
tryAcquireShared
-1 を返して実行しますdoAcquireSharedInterruptibly
。つまり、十分な回数実行されていない場合はcountDownLatch#countDown
、待機キューに入る必要があります。
簡単に言うと、CountDownLatch#await
メソッドが呼び出されたときに、待ちキューを構築するためにカウンターが 0 ではなく、0 の場合は何も実行されません。
ヒント: AQS については、doAcquireSharedInterruptibly
「AQS ファミリーのメンバーの詳細な説明: セマフォ」で分析されているので、繰り返しません~~
エピローグ
CountDownLatch に関する内容はここまでで、内容はそれほど多くありません。AQS や CountDownLatch に慣れていないときは、CountDownLatch が「非常に高度な」ツールであると考えるでしょうが、深く掘り下げてみると、「高度な」テクノロジを学ぶのは実際には難しくないことがわかります。
さて、この記事が役に立った場合は、たくさんの賞賛とサポートをお願いします。最後に、ハードコア テクノロジーを共有する金融漁師とコラム「Java の面接で何を尋ねますか?」にぜひご注目ください。”、また今度!