今回は、JUC のシンクロナイザーのうち、CountDownLatch、CyclicBarrier、Semaphore の3つの主要メンバーについて説明します。
カウントダウンラッチ
直訳すれば、逆カウントラッチ。言うまでもなく、ラッチとはその名の通り、進行をブロックすることを意味します。この場合、CountDownLatch.await()メソッドは、カウントダウンのカウントがゼロになったときに、現在のスレッドをブロックすることを意味します。
は英語の -ity、-ism、-ization に対応します。
CountDownLatchはThread.join()メソッドに似ており、あるスレッドグループと別のスレッドグループの間で連携するために使用できます。たとえば、メイン スレッドが一連の準備を必要とする作業を行っており、これらの準備が完了して初めてメイン スレッドは作業を続行できます。これらの準備は互いに独立しているので、同時に実行して速度を上げることができます。このシナリオでは、CountDownLatchを使用してスレッド間のスケジューリングを調整できます。スレッドを直接生成していた時代には、Thread.join()を使うことができました。JUCの出現後は、スレッドプール内のスレッドを直接参照できないため、CountDownLatchを使用する必要があります。
代表例
次の例は、F1マシンのメンテナンスプロセスとして解釈することができ、メンテナンス作業員はstartSignalコマンドが与えられた後にのみ作業を開始し、すべての作業員が作業を終了したときにのみマシンを続行することができます。
class Driver { // ...
void main() throws InterruptedException {
CountDownLatch startSignal = new CountDownLatch(1);
CountDownLatch doneSignal = new CountDownLatch(N);
for (int i = 0; i < N; ++i) // create and start threads
new Thread(new Worker(startSignal, doneSignal)).start();
doSomethingElse(); // don't let run yet
startSignal.countDown(); // let all threads proceed
doSomethingElse();
doneSignal.await(); // wait for all to finish
}
}
class Worker implements Runnable {
private final CountDownLatch startSignal;
private final CountDownLatch doneSignal;
Worker(CountDownLatch startSignal, CountDownLatch doneSignal) {
this.startSignal = startSignal;
this.doneSignal = doneSignal;
}
public void run() {
try {
startSignal.await();
doWork();
doneSignal.countDown();
} catch (InterruptedException ex) {} // return;
}
void doWork() { ... }
}
startSignal.await()でスレッドがブロックされ、startSignal.countDown()が呼ばれると、すべてのWorkerスレッドがdoWork()メソッドの実行を開始するので、Worker.doWork()はほぼ同時に実行を開始します。Worker.doWork()の実行が終了すると、doneSignal.countDown()が呼び出され、すべてのWorkerスレッドが終了した後もメインスレッドの実行が継続されます。
サイクリック障壁
CyclicBarrierはCyclicFenceやCyclicBarrierなどと訳されます。await()メソッドが呼ばれるたびにカウントが1ずつ減っていき、現在のスレッドはブロックされます。カウントが0になるとブロックが解除され、このCyclicBarrierでブロックしているすべてのスレッドが実行を開始します。再びawait()メソッドが呼ばれると、カウントはN-1になり、新しいラウンドが始まります。
CyclicBarrier の使い方は難しくありませんが、それに関連する主な例外に注意する必要があります。通常の例外に加え、CyclicBarrier.await() メソッドは独自の BrokenBarrierException をスローします。これは、CyclicBarrier を待っているスレッドが割り込まれたり、タイムアウトしたり、リセットされたりした場合に発生し、CyclicBarrier を待っている他のスレッドが BrokenBarrierException を受け取ります。これはつまり、同志たちよ、待つな、小さな友だちはもう死んでしまった、このまま待ち続ければ永遠に待つことになるかもしれない、みんな家に帰れ、ということです。
CyclicBarrier.await()メソッドには、現在のスレッドがバリアに最初に到着したことを示す戻り値があります。
注意: CyclicBarrier 的功能也可以由 CountDownLatch 来实现
代表例
サイクリック障壁の用途
class Solver {
final int N;
final float[][] data;
final CyclicBarrier barrier;
class Worker implements Runnable {
int myRow;
Worker(int row) { myRow = row; }
public void run() {
while (!done()) {
processRow(myRow);
try {
barrier.await();
} catch (InterruptedException ex) {
return;
} catch (BrokenBarrierException ex) {
return;
}
}
}
}
public Solver(float[][] matrix) {
data = matrix;
N = matrix.length;
barrier = new CyclicBarrier(N, new Runnable() {
public void run() {
mergeRows(...);
}
});
for (int i = 0; i < N; ++i)
new Thread(new Worker(i)).start();
waitUntilDone();
}
}
CyclicBarrierとCountDownLatchの使い方の違い
CountDownLatch は、スレッド グループと別のメイン スレッドとの間の連携に使用されます。メイン スレッドが実行を続行する前に、ワーカースレッドのグループがタスクを完了するのを待つのは、CountDownLatch を使用する主なシナリオです。CyclicBarrier は、スレッドのグループまたはスレッドのグループ、たとえば、ジョブを同時に開始するなど、ある時点に合意する必要があるスレッドのグループに使用されます。さらに、CyclicBarrier の周期的な性質と、コンストラクタが受け付ける Runnable パラメータは、CountDownLatch では使用できません。
セマフォ
セマフォは直訳すると信号量ですが、許可量と呼んだ方がわかりやすいかもしれません。もちろん、この名前はコンピュータサイエンスにおいて長い歴史があるので、変更することはできません。機能は比較的簡単で、コンストラクタで許可数を設定し、acquireメソッドで許可数を獲得し、releaseメソッドで許可数を解放します。tryAcquireメソッドやacquireUninterruptiblyメソッドもあるので、必要に応じて使い分けることができます。
例: セマフォによるリソースアクセスの制御
class Pool {
private static final int MAX_AVAILABLE = 100;
private final Semaphore available = new Semaphore(MAX_AVAILABLE, true);
public Object getItem() throws InterruptedException {
available.acquire();
return getNextAvailableItem();
}
public void putItem(Object x) {
if (markAsUnused(x))
available.release();
}
// Not a particularly efficient data structure; just for demo
protected Object[] items = ... whatever kinds of items being managed
protected boolean[] used = new boolean[MAX_AVAILABLE];
protected synchronized Object getNextAvailableItem() {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (!used[i]) {
used[i] = true;
return items[i];
}
}
return null; // not reached
}
protected synchronized boolean markAsUnused(Object item) {
for (int i = 0; i < MAX_AVAILABLE; ++i) {
if (item == items[i]) {
if (used[i]) {
used[i] = false;
return true;
} else
return false;
}
}
return false;
}
}
上記の例では、セマフォの使い方についてあまり説明することはありません。メッセージを残す必要がある2つの同期メソッドがあるということですが、スループットは、メインのブール配列は少しO(n)の操作を行うには、各操作内部のループは非常に単純なので、非常に高速であるため、あまり影響を与えるべきではありません。しかし、私はスレッドプール制御内のJUCがどのように行うかわからない、私は才能がない、私はソースコードのその部分を見ていない、見て時間がある、また言うことができます知っています。
最後に一言
CountDownLatchは、スレッドグループが実行を続ける前に、他のスレッドグループの実行終了を待つことを可能にします。CyclicBarrierは、スレッドグループがある時点で同期に達することを可能にします。セマフォは、一定数のスレッドだけが同時にタスクを実行できるようにするものです。





