Javaスレッドに別のスレッドの出力を待機させる方法は?


128

私は、application-logic-threadとdatabase-access-threadを使用してJavaアプリケーションを作成しています。どちらもアプリケーションの存続期間全体にわたって持続し、両方を同時に実行する必要があります(1つはサーバーと通信し、1つはユーザーと通信します。アプリが完全に起動すると、両方が動作する必要あります)。

ただし、起動時には、アプリスレッドが最初にdbスレッドの準備ができるまで待機することを確認する必要があります(現在、カスタムメソッドのポーリングによって決定されdbthread.isReady()ます)。アプリケーションのスレッドがdbスレッドの準備ができるまでブロックしてもかまいません。

Thread.join() 解決策のようには見えません-dbスレッドはアプリのシャットダウン時にのみ終了します。

while (!dbthread.isReady()) {} 一種の作品ですが、空のループは多くのプロセッササイクルを消費します。

他のアイデアは?ありがとう。

回答:


128

マルチスレッディングの魔法の世界に入る前に、SunのJava同時実行のようなチュートリアルを実行することをお勧めします。

多くの優れた書籍も出ています(「Javaでの並行プログラミング」、「Javaの並行性の実践」のグーグル。

あなたの答えを得るには:

を待機dbThreadする必要があるコードでは、次のようなものが必要です。

//do some work
synchronized(objectYouNeedToLockOn){
    while (!dbThread.isReady()){
        objectYouNeedToLockOn.wait();
    }
}
//continue with work after dbThread is ready

あなたでdbThreadの方法、あなたはこのような何かをする必要があります:

//do db work
synchronized(objectYouNeedToLockOn){
    //set ready flag to true (so isReady returns true)
    ready = true;
    objectYouNeedToLockOn.notifyAll();
}
//end thread run method here

objectYouNeedToLockOn私はこれらの例で使用しているあなたは、各スレッドから同時に操作する必要があることが好ましい対象であるか、別々に作成することができますObject(私は自分自身が同期メソッドを作るお勧めしません)、その目的のために:

private final Object lock = new Object();
//now use lock in your synchronized blocks

あなたの理解を深めるために:
上記を行うには、他にも(時にはより良い)方法がありますCountdownLatches。たとえば、Java 5などです。Java5以降、java.util.concurrentパッケージとサブパッケージには気の利いた並行処理クラスがたくさんあります。並行性を理解したり、良い本を入手したりするには、オンラインで資料を見つける必要があります。


私が間違っていなければ、すべてのスレッドコードをオブジェクトにうまく統合することもできません。したがって、オブジェクトの同期を使用することは、このスレッド関連の作業を実装するための良い方法ではないと思います。
user1914692 2013年

@ user1914692:上記のアプローチを使用する際の落とし穴は何かわかりません。詳しく説明してください。
Piskvorが建物を去る2014

1
@Piskvor:すみません、これはずっと前に書いたもので、私は自分の考えをほとんど忘れていました。おそらく、オブジェクトの同期よりもロックを使用するほうがいいということでした。
user1914692 2014年

これがどのように機能するのかわかりません。スレッドaがオブジェクトを待機している場合、synchronised(object)別のスレッドはどのsynchronized(object)ようにしてobject.notifyAll()?私のプログラムでは、すべてがsynchronozedブロックに行き詰まっています。
トマーシュZato -復活モニカ

@TomášZato最初のスレッドはobject.wait()、そのオブジェクトのロックを効果的にロック解除するために呼び出します。2番目のスレッドが同期ブロックを「終了」すると、他のオブジェクトがwaitメソッドから解放され、その時点でロックが再取得されます。
rogerdpack 2015

140

CountDownLatchを1のカウンターで使用します。

CountDownLatch latch = new CountDownLatch(1);

アプリスレッドで

latch.await();

dbスレッドで、完了したら、次を実行します-

latch.countDown();

4
コードの意味を一目で把握するのは難しいかもしれませんが、私はそのソリューションが単純であるため本当に気に入っています。
致命的なギター

3
この使用法では、使い果たされたときにラッチを再作成する必要があります。Windowsで待機可能イベントに類似の使用状況を取得するには、BooleanLatch、またはリセットされたCountDownLatchを試してみてください: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/...の stackoverflow.com/questionsを/ 6595835 /…
phyatt 14

1
こんにちは、私が最初にそのようなイベントを発生させるはずの非同期メソッドを呼び出した場合:1)asyncFunc(); 2)latch.await(); 次に、受信したイベント処理関数でカウントダウンします。どうすればラッチ.await()が呼び出される前にイベントが処理されないようにできますか?1行目と2行目のプリエンプションを防止したいと思います。ありがとうございます。
NioX5199

1
エラーが発生した場合に永遠に待機しないようにするにはcountDown()finally{}ブロックに入れます
Daniel Alder

23

要件::

  1. 前のスレッドが終了するまで次のスレッドの実行を待機します。
  2. 時間の消費に関係なく、次のスレッドは前のスレッドが停止するまで開始してはなりません。
  3. シンプルで使いやすいものでなければなりません。

回答::

@ java.util.concurrent.Future.get()ドキュメントを参照してください。

future.get()必要に応じて計算が完了するのを待ってから、結果を取得します。

仕事完了!! 以下の例を参照してください

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

public class ThreadTest {

    public void print(String m) {
        System.out.println(m);
    }

    public class One implements Callable<Integer> {

        public Integer call() throws Exception {
            print("One...");
            Thread.sleep(6000);
            print("One!!");
            return 100;
        }
    }

    public class Two implements Callable<String> {

        public String call() throws Exception {
            print("Two...");
            Thread.sleep(1000);
            print("Two!!");
            return "Done";
        }
    }

    public class Three implements Callable<Boolean> {

        public Boolean call() throws Exception {
            print("Three...");
            Thread.sleep(2000);
            print("Three!!");
            return true;
        }
    }

    /**
     * @See java.util.concurrent.Future.get() doc
     *      <p>
     *      Waits if necessary for the computation to complete, and then
     *      retrieves its result.
     */
    @Test
    public void poolRun() throws InterruptedException, ExecutionException {
        int n = 3;
        // Build a fixed number of thread pool
        ExecutorService pool = Executors.newFixedThreadPool(n);
        // Wait until One finishes it's task.
        pool.submit(new One()).get();
        // Wait until Two finishes it's task.
        pool.submit(new Two()).get();
        // Wait until Three finishes it's task.
        pool.submit(new Three()).get();
        pool.shutdown();
    }
}

このプログラムの出力::

One...
One!!
Two...
Two!!
Three...
Three!!

他のスレッドよりもタスクが完了するまでに6秒かかることがわかります。したがって、Future.get()はタスクが完了するまで待機します。

future.get()を使用しない場合は、終了するまで待機せず、時間ベースの消費を実行します。

Java同時実行による幸運。


お返事ありがとうございます!私はCountdownLatches を使用しましたが、あなたの方法ははるかに柔軟なアプローチです。
Piskvorが建物を去る

9

正解の多くが、簡単な例なし...ここでは、使用するどのように簡単でシンプルな方法ですCountDownLatch

//inside your currentThread.. lets call it Thread_Main
//1
final CountDownLatch latch = new CountDownLatch(1);

//2
// launch thread#2
new Thread(new Runnable() {
    @Override
    public void run() {
        //4
        //do your logic here in thread#2

        //then release the lock
        //5
        latch.countDown();
    }
}).start();

try {
    //3 this method will block the thread of latch untill its released later from thread#2
    latch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//6
// You reach here after  latch.countDown() is called from thread#2

8
public class ThreadEvent {

    private final Object lock = new Object();

    public void signal() {
        synchronized (lock) {
            lock.notify();
        }
    }

    public void await() throws InterruptedException {
        synchronized (lock) {
            lock.wait();
        }
    }
}

このようにこのクラスを使用してください:

ThreadEventを作成します。

ThreadEvent resultsReady = new ThreadEvent();

メソッドでは、これは結果を待っています:

resultsReady.await();

そして、すべての結果が作成された後に結果を作成しているメソッドでは:

resultsReady.signal();

編集:

(この投稿を編集して申し訳ありませんが、このコードには非常に悪い競合状態があり、コメントするのに十分な評判がありません)

これは、await()の後にsignal()が呼び出されることが100%確実である場合にのみ使用できます。これが、WindowsイベントなどのJavaオブジェクトを使用できない大きな理由の1つです。

コードがこの順序で実行される場合:

Thread 1: resultsReady.signal();
Thread 2: resultsReady.await();

その後、スレッド2は永久に待機します。これは、Object.notify()が現在実行中のスレッドの1つだけを起こすためです。後で待機しているスレッドは起こされません。これは、イベントが機能することを期待する方法とは大きく異なります。イベントは、a)待機するか、b)明示的にリセットされるまで通知されます。

注:ほとんどの場合、notifyAll()を使用する必要がありますが、これは上記の「永久に待機する」問題には関係ありません。


7

CountDownLatchクラスをjava.util.concurrentパッケージから試してみてください。これにより、高レベルの同期メカニズムが提供され、低レベルのものよりもエラーが発生しにくくなります。


6

これは、2つのスレッド間で共有されるExchangerオブジェクトを使用して行うことができます。

private Exchanger<String> myDataExchanger = new Exchanger<String>();

// Wait for thread's output
String data;
try {
  data = myDataExchanger.exchange("");
} catch (InterruptedException e1) {
  // Handle Exceptions
}

そして2番目のスレッドで:

try {
    myDataExchanger.exchange(data)
} catch (InterruptedException e) {

}

他の人が言ったように、この気軽なコードをコピーして貼り付けないでください。最初にいくつか読んでください。


4

パッケージのFutureインターフェースは、java.lang.concurrent別のスレッドで計算された結果へのアクセスを提供するように設計されています。

この種のことを行う既製の方法については、FutureTaskExecutorServiceご覧ください。

並行性とマルチスレッドに興味がある人には、Java Concurrency In Practiceを読むことを強くお勧めします。それは明らかにJavaに集中していますが、他の言語で作業する人にとっても多くの肉があります。


2

すばやく簡単なものが必要な場合は、whileループ内にThread.sleep()呼び出しを追加するだけです。データベースライブラリが変更できないものである場合、他に簡単な解決策はありません。待機期間を設ける準備ができるまでデータベースをポーリングしても、パフォーマンスが低下することはありません。

while (!dbthread.isReady()) {
  Thread.sleep(250);
}

エレガントなコードとは言えないかもしれませんが、作業は完了です。

データベースコードを変更できる場合は、他の回答で提案されているようにmutexを使用することをお勧めします。


3
これはかなり待っているだけで忙しいです。Java 5のutil.concurrentパッケージの構成要素を使用するのがよいでしょう。stackoverflow.com/questions/289434/…今のところ、最善の解決策として私に見えます。
Cem Catikkas 2008年

待機中ですが、この特定の場所でのみ必要であり、dbライブラリにアクセスできない場合、他に何ができますか?忙しい待機は必ずしも悪いことではありません
マリオオルテゴン

2

これはすべての言語に適用されます:

イベント/リスナーモデルが必要です。特定のイベントを待機するリスナーを作成します。イベントはワーカースレッドで作成(または通知)されます。これにより、現在のソリューションのように、条件が満たされているかどうかを常にポーリングするのではなく、シグナルが受信されるまでスレッドがブロックされます。

状況は、デッドロックの最も一般的な原因の1つです。発生したエラーに関係なく、他のスレッドにシグナルを送信するようにしてください。例-アプリケーションが例外をスローし、メソッドが呼び出されて、完了したことを他のユーザーに通知しない場合。これにより、他のスレッドが「ウェイクアップ」しないようになります。

ケースを実装する前に、このパラダイムをよりよく理解するために、イベントとイベントハンドラーの使用の概念を検討することをお勧めします。

または、mutex-を使用してブロッキング関数呼び出しを使用できます。これにより、リソースが解放されるまでスレッドが待機します。これを行うには、次のような適切なスレッド同期が必要です。

Thread-A Locks lock-a
Run thread-B
Thread-B waits for lock-a
Thread-A unlocks lock-a (causing Thread-B to continue)
Thread-A waits for lock-b 
Thread-B completes and unlocks lock-b

2

あるスレッドでブロッキングキューから読み取り、別のスレッドで書き込むことができます。


1

以来

  1. join() 除外されました
  2. あなたはすでにCountDownLatchを使用しており、
  3. Future.get()はすでに他の専門家によって提案されています、

他の代替案を検討できます。

  1. からinvokeAllExecutorService

    invokeAll(Collection<? extends Callable<T>> tasks)

    指定されたタスクを実行し、すべてが完了すると、ステータスと結果を保持するFutureのリストを返します。

  2. ForkJoinPoolまたはnewWorkStealingPool from Executors(Java 8リリース以降)

    使用可能なすべてのプロセッサをターゲットの並列処理レベルとして使用して、ワークスチールスレッドプールを作成します。


-1

ここに画像の説明を入力してください

このアイデアは適用できますか?CountdownLatchesまたはSemaphoresを使用すると完璧に機能しますが、インタビューの最も簡単な答えを探している場合、これは当てはまると思います。


1
これはインタビューでは大丈夫ですか、実際のコードでは大丈夫ですか?
Piskvorが2016

この場合、順番に実行されているため、別の待機が1つあります。ここで与えられた最良の答えであるCountdownLatchesを使用するとスレッドがスリープ状態にならない、つまりCPUサイクルの使用を意味するため、セマフォを使用するのが最良のソリューションである可能性があります。
フランコ

しかし、ポイントは「それらを順番に実行する」ことではありませんでした。質問を編集してこれをより明確にします:GUIスレッドはデータベースの準備ができるまで待機し、その後、アプリの残りの実行では両方が同時に実行れます:GUIスレッドはコマンドをDBスレッドに送信して結果を読み取ります。(繰り返しになりますが、実際のコードではなく、インタビューで使用できるコードのポイントは何ですか?私が会ったほとんどのテクニカルインタビュアーは、コードのバックグラウンドを持っていて、同じ質問をします。さらに、実際のアプリでこれが必要でした私は当時、宿題のイタチ用ではありませんでした)
Piskvorは、

1
そう。これは、セマフォを使用するプロデューサーコンシューマーの問題です。私は一つの例を試してみます
フランコ

プロジェクトgithub.com/francoj22/SemProducerConsumer/blob/master/src/com/…を作成しました。正常に動作します。
フランコ
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.