序章
スレッドというのは興味深いものです。過去のレビューでは、マルチスレッドの実装に利用可能なツールのいくつかを検討しました。他にどんな面白いことができるか見てみましょう。現時点では、私たちは多くのことを知っています。たとえば、「
Better together: Java と Thread クラス。パート I — 実行のスレッド」から、Thread クラスが実行のスレッドを表すことがわかります。スレッドが何らかのタスクを実行することはわかっています。タスクに を実行できるようにしたい場合は
run
、スレッドを でマークする必要があります
Runnable
。
覚えておくために、 Tutorialspoint Online Java Compilerを使用できます。
public static void main(String[] args){
Runnable task = () -> {
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
};
Thread thread = new Thread(task);
thread.start();
}
また、ロックと呼ばれるものがあることもわかっています。
これについては、「 Better together: Java と Thread クラス。パート II — 同期」で学びました。あるスレッドがロックを取得すると、ロックを取得しようとする別のスレッドはロックが解放されるまで待機することになります。
import java.util.concurrent.locks.*;
public class HelloWorld{
public static void main(String []args){
Lock lock = new ReentrantLock();
Runnable task = () -> {
lock.lock();
Thread thread = Thread.currentThread();
System.out.println("Hello from " + thread.getName());
lock.unlock();
};
Thread thread = new Thread(task);
thread.start();
}
}
他にどんな興味深いことができるかについて話し合う時期が来たと思います。
セマフォ
同時に実行できるスレッドの数を制御する最も簡単な方法は、セマフォです。鉄道の信号機のようなものです。緑色は続行を意味します。赤は待つことを意味します。セマフォから何を待っていますか? アクセス。アクセスするには、それを取得する必要があります。そして、アクセスが必要なくなったら、それを譲渡するか解放する必要があります。これがどのように機能するかを見てみましょう。クラスをインポートする必要があります
java.util.concurrent.Semaphore
。例:
public static void main(String[] args) throws InterruptedException {
Semaphore semaphore = new Semaphore(0);
Runnable task = () -> {
try {
semaphore.acquire();
System.out.println("Finished");
semaphore.release();
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
Thread.sleep(5000);
semaphore.release(1);
}
ご覧のとおり、これらの操作 (取得と解放) は、セマフォがどのように機能するかを理解するのに役立ちます。最も重要なことは、アクセスを取得するには、セマフォに正の数の許可が必要であるということです。このカウントは負の数に初期化できます。また、複数の許可を申請(取得)することもできます。
カウントダウンラッチ
次の仕組みは です
CountDownLatch
。当然のことながら、これはカウントダウン付きのラッチです。ここでは、クラスに適切な import ステートメントが必要です
java.util.concurrent.CountDownLatch
。スタートラインに全員が集まる徒競走のようなものです。そして全員の準備が整ったら、全員が同時にスタートの合図を受けて一斉にスタートします。例:
public static void main(String[] args) {
CountDownLatch countDownLatch = new CountDownLatch(3);
Runnable task = () -> {
try {
countDownLatch.countDown();
System.out.println("Countdown: " + countDownLatch.getCount());
countDownLatch.await();
System.out.println("Finished");
} catch (InterruptedException e) {
e.printStackTrace();
}
};
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
まず、最初にラッチに を指示します
countDown()
。Googleではカウントダウンを「数字を逆順にゼロまで数えていく行為」と定義しています。そして、ラッチに
await()
「カウンタがゼロになるまで待つ」ように指示します。興味深いことに、これは 1 回限りのカウンターです。Java のドキュメントには、「スレッドがこの方法で繰り返しカウントダウンする必要がある場合は、代わりに CyclicBarrier を使用してください」と記載されています。つまり、再利用可能なカウンターが必要な場合は、別のオプションが必要になります
CyclicBarrier
。
サイクリックバリア
名前が示すように、
CyclicBarrier
「再利用可能な」バリアです。クラスをインポートする必要があります
java.util.concurrent.CyclicBarrier
。例を見てみましょう:
public static void main(String[] args) throws InterruptedException {
Runnable action = () -> System.out.println("On your mark!");
CyclicBarrier barrier = new CyclicBarrier(3, action);
Runnable task = () -> {
try {
barrier.await();
System.out.println("Finished");
} catch (BrokenBarrierException | InterruptedException e) {
e.printStackTrace();
}
};
System.out.println("Limit: " + barrier.getParties());
for (int i = 0; i < 3; i++) {
new Thread(task).start();
}
}
ご覧のとおり、スレッドは
await
メソッドを実行します。つまり、待機します。この場合、バリア値は減少する。カウントダウンがゼロになると、バリアは壊れたと見なされます (
barrier.isBroken()
)。バリアをリセットするには、メソッドを呼び出す必要があります
reset()
が、その
CountDownLatch
必要はありません。
エクスチェンジャー
次のメカニズムは Exchanger です。この文脈において、Exchange は、変更が交換または交換される同期ポイントです。ご想像のとおり、an は
Exchanger
交換またはスワップを実行するクラスです。最も単純な例を見てみましょう。
public static void main(String[] args) {
Exchanger<String> exchanger = new Exchanger<>();
Runnable task = () -> {
try {
Thread thread = Thread.currentThread();
String withThreadName = exchanger.exchange(thread.getName());
System.out.println(thread.getName() + " exchanged with " + withThreadName);
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
new Thread(task).start();
}
ここで 2 つのスレッドを開始します。それぞれが交換メソッドを実行し、他のスレッドも交換メソッドを実行するのを待ちます。その際、スレッドは渡された引数を交換します。面白い。何かを思い出しませんか?
SynchronousQueue
の中心にあるを彷彿とさせます
CachedThreadPool
。わかりやすくするために、例を示します。
public static void main(String[] args) throws InterruptedException {
SynchronousQueue<String> queue = new SynchronousQueue<>();
Runnable task = () -> {
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
e.printStackTrace();
}
};
new Thread(task).start();
queue.put("Message");
}
この例は、新しいスレッドが開始されると、キューが空になるため待機することを示しています。そして、メインスレッドは「メッセージ」文字列をキューに入れます。さらに、この文字列がキューから受信されるまで停止します。このトピックの詳細については、 「
SynchronousQueue vs Exchanger 」を参照してください。
フェイザー
最高のものを最後に取っておきました —
Phaser
。クラスをインポートする必要があります
java.util.concurrent.Phaser
。簡単な例を見てみましょう。
public static void main(String[] args) throws InterruptedException {
Phaser phaser = new Phaser();
// By calling the register method, we register the current (main) thread as a party
phaser.register();
System.out.println("Phasecount is " + phaser.getPhase());
testPhaser(phaser);
testPhaser(phaser);
testPhaser(phaser);
// After 3 seconds, we arrive at the barrier and deregister. Number of arrivals = number of registrations = start
Thread.sleep(3000);
phaser.arriveAndDeregister();
System.out.println("Phasecount is " + phaser.getPhase());
}
private static void testPhaser(final Phaser phaser) {
// We indicate that there will be a +1 party on the Phaser
phaser.register();
// Start a new thread
new Thread(() -> {
String name = Thread.currentThread().getName();
System.out.println(name + " arrived");
phaser.arriveAndAwaitAdvance(); // The threads register arrival at the phaser.
System.out.println(name + " after passing barrier");
}).start();
}
この例は、 を使用した場合
Phaser
、登録数がバリアへの到着数と一致するとバリアが壊れることを示しています。
この GeeksforGeeks の記事をPhaser
読むことで、さらに詳しく知ることができます。
まとめ
これらの例からわかるように、スレッドを同期するにはさまざまな方法があります。先ほど、マルチスレッドの側面を思い出そうとしました。このシリーズのこれまでの記事がお役に立てば幸いです。マルチスレッドへの道は「Java Concurrency in Practice」という本から始まるという人もいます。この本は 2006 年に出版されましたが、この本は非常に基礎的であり、現在でも有効であると言われています。たとえば、ここで議論を読むことができます:
「Java Concurrency In Practice」はまだ有効ですか? 。ディスカッション内のリンクを読むことも役立ちます。たとえば、書籍
「The Well-Grounded Java Developer」へのリンクがあり、特に第 4 章「最新の同時実行性」について言及します。このトピックに関する全体的なレビューもあります。
「実際の Java 同時実行性」は Java 8 の時代でも有効ですか? この記事では、このトピックを真に理解するために他に何を読むべきかについての提案も提供しています。その後、
OCA/OCP Java SE 8 Programmer Practice Testsのような素晴らしい本を読んでみてください。2 番目の頭字語である OCP (Oracle Certified Professional) に興味があります。テストは「第 20 章: Java 同時実行性」にあります。この本には、問題と解答の両方が解説付きで掲載されています。例:
この質問はメソッドの暗記のもう 1 つの例であると多くの人が言い始めるかもしれません。一方では、そうです。一方、これが の
ExecutorService
一種の「アップグレード」であることを思い出せば、この質問に答えることができます
Executor
。と
Executor
これはスレッドの作成方法を単に隠すことを目的としていますが、スレッドを実行する主な方法、つまり
Runnable
新しいスレッドでオブジェクトを開始する方法ではありません。がないのはそのためです。
execute(Callable)
では
ExecutorService
、オブジェクトを返すことができるメソッドを
Executor
単に追加しているだけだからです。もちろん、メソッドのリストを暗記することもできますが、クラス自体の性質についての知識に基づいて答えを作成する方がはるかに簡単です。このトピックに関する追加資料は次のとおりです。
submit()
Future
Java と Thread クラスを組み合わせるとさらに効果的です。パート I — 実行スレッド 組み合わせるとさらに効果的: Java と Thread クラス。パート II — 同期 併用するとより効果的です: Java と Thread クラス。パート III — 連携 を強化: Java と Thread クラス。パート IV — Callable、Future、およびその仲間たち 一緒にさらに良く: Java と Thread クラス。パート V — エグゼキュータ、スレッドプール、フォーク/ジョイン
GO TO FULL VERSION