誰かがJava CountDownLatch
とは何か、いつそれを使用するかを理解するのを助けることができますか?
このプログラムがどのように機能するかについて、私にはあまり明確な考えがありません。私が理解しているように、3つのスレッドはすべて一度に開始し、各スレッドは3000ミリ秒後にCountDownLatchを呼び出します。したがって、カウントダウンは1つずつ減少します。ラッチがゼロになった後、プログラムは「Completed」を出力します。たぶん私が理解した方法は間違っています。
import Java.util.concurrent.CountDownLatch;
import Java.util.concurrent.ExecutorService;
import Java.util.concurrent.Executors;
class Processor implements Runnable {
private CountDownLatch latch;
public Processor(CountDownLatch latch) {
this.latch = latch;
}
public void run() {
System.out.println("Started.");
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
latch.countDown();
}
}
// ------------------------------------------------ -----
public class App {
public static void main(String[] args) {
CountDownLatch latch = new CountDownLatch(3); // coundown from 3 to 0
ExecutorService executor = Executors.newFixedThreadPool(3); // 3 Threads in pool
for(int i=0; i < 3; i++) {
executor.submit(new Processor(latch)); // ref to latch. each time call new Processes latch will count down by 1
}
try {
latch.await(); // wait until latch counted down to 0
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Completed.");
}
}
はい、あなたは正しく理解しました。 CountDownLatch
はラッチ原理で動作し、メインスレッドはゲートが開くまで待機します。 1つのスレッドは、CountDownLatch
の作成中に指定されたnスレッドを待機します。
CountDownLatch.await()
を呼び出すすべてのスレッド(通常はアプリケーションのメインスレッド)は、countがゼロに達するか、別のスレッドによって中断されるまで待機します。他のすべてのスレッドは、完了または準備ができたらCountDownLatch.countDown()
を呼び出してカウントダウンする必要があります。
カウントがゼロになるとすぐに、待機中のスレッドが継続します。 CountDownLatch
のデメリット/利点の1つは、再利用できないことです。カウントがゼロに達すると、CountDownLatch
を使用できなくなります。
編集:
1つのスレッド(メインスレッドなど)が1つ以上のスレッドの完了を待機する必要がある場合は、CountDownLatch
を使用してから処理を続行できます。
JavaでCountDownLatch
を使用する古典的な例は、サービスアーキテクチャを使用するサーバー側コアJavaアプリケーションです。複数のスレッドによって複数のサービスが提供され、すべてのサービスが正常に開始されるまでアプリケーションは処理を開始できません。
追伸OPの質問には非常に単純な例があるため、ここには含めませんでした。
JavaのCountDownLatch
は、1つのThread
が処理を開始する前に1つ以上のThread
sを待機できる同期装置の一種です。
CountDownLatch
はラッチ原理で動作し、スレッドはゲートが開くまで待機します。 1つのスレッドは、n
の作成中に指定されたCountDownLatch
個のスレッドを待機します。
例えばfinal CountDownLatch latch = new CountDownLatch(3);
ここでは、カウンターを3に設定します。
CountDownLatch.await()
を呼び出すスレッド(通常はアプリケーションのメインスレッド)は、カウントがゼロになるか、別のThread
によって中断されるまで待機します。他のすべてのスレッドは、完了またはジョブの準備ができたらCountDownLatch.countDown()
を呼び出してカウントダウンする必要があります。カウントがゼロになるとすぐに、Thread
待機が実行を開始します。
ここで、カウントはCountDownLatch.countDown()
メソッドによって減分されます。
await()
メソッドを呼び出すThread
は、初期カウントがゼロに達するまで待機します。
カウントをゼロにするには、他のスレッドがcountDown()
メソッドを呼び出す必要があります。カウントがゼロになると、await()
メソッドを呼び出したスレッドは再開(実行を開始)します。
CountDownLatch
の欠点は、再利用できないことです。カウントがゼロになると、使用できなくなります。
NikolaBはそれを非常にうまく説明しましたが、例を理解することは助けになるでしょう、それでここに1つの簡単な例を示します...
import Java.util.concurrent.*;
public class CountDownLatchExample {
public static class ProcessThread implements Runnable {
CountDownLatch latch;
long workDuration;
String name;
public ProcessThread(String name, CountDownLatch latch, long duration){
this.name= name;
this.latch = latch;
this.workDuration = duration;
}
public void run() {
try {
System.out.println(name +" Processing Something for "+ workDuration/1000 + " Seconds");
Thread.sleep(workDuration);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(name+ "completed its works");
//when task finished.. count down the latch count...
// basically this is same as calling lock object notify(), and object here is latch
latch.countDown();
}
}
public static void main(String[] args) {
// Parent thread creating a latch object
CountDownLatch latch = new CountDownLatch(3);
new Thread(new ProcessThread("Worker1",latch, 2000)).start(); // time in millis.. 2 secs
new Thread(new ProcessThread("Worker2",latch, 6000)).start();//6 secs
new Thread(new ProcessThread("Worker3",latch, 4000)).start();//4 secs
System.out.println("waiting for Children processes to complete....");
try {
//current thread will get notified if all chidren's are done
// and thread will resume from wait() mode.
latch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("All Process Completed....");
System.out.println("Parent Thread Resuming work....");
}
}
複数のスレッドがタスクを完了するまで待機する場合に使用されます。スレッドでの結合に似ています。
CountDownLatchを使用できる場所
3つのスレッド「A」、「B」、および「C」があり、「A」および「B」スレッドがタスクを完了または部分的に完了した場合にのみスレッド「C」を開始するという要件があるシナリオを考えます。
実際のITシナリオに適用可能
マネージャーが開発チーム(AとB)の間でモジュールを分割し、両方のチームがタスクを完了したときにのみテストのためにQAチームに割り当てたいというシナリオを考えます。
public class Manager {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(2);
MyDevTeam teamDevA = new MyDevTeam(countDownLatch, "devA");
MyDevTeam teamDevB = new MyDevTeam(countDownLatch, "devB");
teamDevA.start();
teamDevB.start();
countDownLatch.await();
MyQATeam qa = new MyQATeam();
qa.start();
}
}
class MyDevTeam extends Thread {
CountDownLatch countDownLatch;
public MyDevTeam (CountDownLatch countDownLatch, String name) {
super(name);
this.countDownLatch = countDownLatch;
}
@Override
public void run() {
System.out.println("Task assigned to development team " + Thread.currentThread().getName());
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("Task finished by development team Thread.currentThread().getName());
this.countDownLatch.countDown();
}
}
class MyQATeam extends Thread {
@Override
public void run() {
System.out.println("Task assigned to QA team");
try {
Thread.sleep(2000);
} catch (InterruptedException ex) {
ex.printStackTrace();
}
System.out.println("Task finished by QA team");
}
}
上記のコードの出力は次のようになります:
開発チームdevBに割り当てられたタスク
開発チームdevAに割り当てられたタスク
開発チームdevBがタスクを完了
開発チームdevAによってタスクが完了しました
QAチームに割り当てられたタスク
QAチームが完了したタスク
ここでawait()メソッドはcountdownlatchフラグが0になるのを待ち、countDown()メソッドはcountdownlatchフラグを1減らします。
JOINの制限:上記の例はJOINでも実現できますが、JOINは2つのシナリオでは使用できません:
CountDownLatch に関するOracleドキュメントから:
他のスレッドで実行されている一連の操作が完了するまで、1つ以上のスレッドが待機できるようにする同期支援。
CountDownLatch
name__は、指定されたカウントで初期化されます。 await
name__メソッドは、countDown()
メソッドの呼び出しにより現在のカウントがゼロになるまでブロックします。その後、待機中のすべてのスレッドが解放され、その後のawaitの呼び出しが直ちに返されます。これはワンショット現象です。カウントはリセットできません。
CountDownLatchは多用途の同期ツールであり、さまざまな目的に使用できます。
1のカウントで初期化されたCountDownLatch
name__は、単純なオン/オフラッチ、またはゲートとして機能します。待機を呼び出すすべてのスレッドは、countDown()を呼び出すスレッドによって開かれるまで、ゲートで待機します。
Nに初期化されたCountDownLatch
name__を使用すると、N個のスレッドが何らかのアクションを完了するまで、または何らかのアクションがN回完了するまで1つのスレッドを待機させることができます。
public void await()
throws InterruptedException
スレッドが中断されない限り、ラッチがゼロまでカウントダウンするまで現在のスレッドを待機させます。
現在のカウントがゼロの場合、このメソッドはすぐに戻ります。
public void countDown()
ラッチのカウントをデクリメントし、カウントがゼロに達すると待機中のスレッドをすべて解放します。
現在のカウントがゼロより大きい場合、デクリメントされます。新しいカウントがゼロの場合、待機中のすべてのスレッドはスレッドスケジューリングのために再度有効になります。
あなたの例の説明。
latch
name__変数のカウントを3に設定しました
CountDownLatch latch = new CountDownLatch(3);
この共有latch
name__をワーカースレッドに渡しました:Processor
name__
Runnable
name__の3つのProcessor
name__インスタンスがExecutorService
executor
name__に送信されましたメインスレッド(App
name__)は、以下のステートメントでカウントがゼロになるのを待っています
latch.await();
Processor
name__スレッドは3秒間スリープし、その後latch.countDown()
でカウント値をデクリメントします最初のProcess
name__インスタンスは、latch.countDown()
が原因で完了後にラッチカウントを2に変更します。
2番目のProcess
name__インスタンスは、latch.countDown()
が原因で完了した後、ラッチカウントを1に変更します。
3番目のProcess
name__インスタンスは、latch.countDown()
が原因で完了すると、ラッチカウントを0に変更します。
ラッチのゼロカウントにより、メインスレッドApp
name__がawait
name__から出てきます。
アプリプログラムはこの出力をすぐに出力します:Completed
name__
Clamp.countDown()を呼び出した後にデバッグを追加すると、その動作をよりよく理解するのに役立ちます。
latch.countDown();
System.out.println("DONE "+this.latch); // Add this debug
出力には、カウントが減少していることが示されます。この「カウント」は、実際には、countDown()がnot呼び出されたためにブロックされたときに開始したRunnableタスク(Processorオブジェクト)の数ですメインスレッドは、ratch.await()の呼び出しで。
DONE Java.util.concurrent.CountDownLatch@70e69696[Count = 2]
DONE Java.util.concurrent.CountDownLatch@70e69696[Count = 1]
DONE Java.util.concurrent.CountDownLatch@70e69696[Count = 0]
CoundDownLatchを使用すると、他のすべてのスレッドの実行が完了するまでスレッドを待機させることができます。
擬似コードには次のものがあります。
// Main thread starts
// Create CountDownLatch for N threads
// Create and start N threads
// Main thread waits on latch
// N threads completes there tasks are returns
// Main thread resume execution
このようなものを使用する良い例の1つは、シリアルポートにアクセスするJava Simple Serial Connectorを使用する場合です。通常、ポートに何かを書き込むと、非同期で、別のスレッドで、デバイスはSerialPortEventListenerで応答します。通常、ポートへの書き込み後に一時停止して、応答を待機します。このシナリオのスレッドロックを手動で処理するのは非常に難しいですが、Countdownlatchの使用は簡単です。あなたが別の方法でそれを行うことができると考える前に、あなたが考えもしなかったレース状態に注意してください!!
擬似コード:
CountDownLatch latch;
void writeData() {
latch = new CountDownLatch(1);
serialPort.writeBytes(sb.toString().getBytes())
try {
latch.await(4, TimeUnit.SECONDS);
} catch (InterruptedException e) {
}
}
class SerialPortReader implements SerialPortEventListener {
public void serialEvent(SerialPortEvent event) {
if(event.isRXCHAR()){//If data is available
byte buffer[] = serialPort.readBytes(event.getEventValue());
latch.countDown();
}
}
}
Java Doc のこの例は、概念を明確に理解するのに役立ちました。
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() { ... }
}
図式的に言えば:
明らかに、CountDownLatch
を使用すると、1つのスレッド(ここではDriver
)が実行中のスレッド(ここではWorker
)の実行が完了するまで待機できます。
JavaDoc( https://docs.Oracle.com/javase/7/docs/api/Java/util/concurrent/CountDownLatch.html )で述べたように、CountDownLatchはJavaで導入された同期支援です。 5.ここで、同期とは、クリティカルセクションへのアクセスを制限することではありません。しかし、異なるスレッドのアクションを順番に並べます。 CountDownLatchによって達成される同期のタイプは、Joinのタイプと似ています。他のワーカースレッド「T1」、「T2」、「T3」がタスクを完了するのを待機する必要があるスレッド「M」があると仮定します。Java1.5より前の方法は、Mが次のコードを実行することです。
T1.join();
T2.join();
T3.join();
上記のコードは、T1、T2、T3が作業を完了した後、スレッドMが作業を再開することを確認します。 T1、T2、T3は、任意の順序で作業を完了することができます。 T1、T2、T3、およびスレッドMが同じCountDownLatchオブジェクトを共有するCountDownLatchでも同じことが実現できます。
「M」リクエスト:countDownLatch.await();
「T1」、「T2」、「T3」はcountDownLatch.countdown();
結合方法の欠点の1つは、MがT1、T2、T3について知る必要があることです。後で追加された新しいワーカースレッドT4がある場合、Mもそれに注意する必要があります。これはCountDownLatchで回避できます。実装後、アクションのシーケンスは[T1、T2、T3]になります(T1、T2、T3の順序はとにかく可能性があります)-> [M]
package practice;
import Java.util.concurrent.CountDownLatch;
public class CountDownLatchExample {
public static void main(String[] args) throws InterruptedException {
CountDownLatch c= new CountDownLatch(3); // need to decrements the count (3) to zero by calling countDown() method so that main thread will wake up after calling await() method
Task t = new Task(c);
Task t1 = new Task(c);
Task t2 = new Task(c);
t.start();
t1.start();
t2.start();
c.await(); // when count becomes zero main thread will wake up
System.out.println("This will print after count down latch count become zero");
}
}
class Task extends Thread{
CountDownLatch c;
public Task(CountDownLatch c) {
this.c = c;
}
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName());
Thread.sleep(1000);
c.countDown(); // each thread decrement the count by one
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
このリンクで説明されているcountDownLatchのベストリアルタイムの例 CountDownLatchExample