如何让Java线程等待另一个线程的输出?

时间:2008-11-14 07:29:37

标签: java multithreading

我正在使用应用程序逻辑线程和数据库访问线程创建Java应用程序。 它们都在应用程序的整个生命周期中持续存在,并且两者都需要同时运行(一个与服务器通信,一个与用户通信;当应用程序完全启动时,我需要两个他们工作)。

但是,在启动时,我需要确保最初应用程序线程等待,直到db线程准备就绪(当前通过轮询自定义方法dbthread.isReady()确定)。 我不介意app线程阻塞,直到db线程准备就绪。

Thread.join()看起来不像解决方案 - 数据库线程仅在应用程序关闭时退出。

while (!dbthread.isReady()) {}有点工作,但空循环会消耗很多处理器周期。

还有其他想法吗?感谢。

13 个答案:

答案 0 :(得分:132)

使用计数器为1的CountDownLatch

CountDownLatch latch = new CountDownLatch(1);

现在在app线程中执行 -

latch.await();

在db线程中,完成后,执行 -

latch.countDown();

答案 1 :(得分:126)

我建议您在开始进行多线程的神奇世界之前先阅读Sun's Java Concurrency之类的教程。

还有很多好书(google为“Java中的并发编程”,“Java Concurrency in Practice”。

要得到答案:

在必须等待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开始,java.util.concurrent包和子包中有很多漂亮的并发类。你真的需要在网上找到材料来了解并发性,或者获得一本好书。

答案 2 :(得分:22)

要求::

  
      
  1. 等待执行下一个线程,直到上一个完成。
  2.   
  3. 下一个线程必须在前一个线程停止之前才能启动,无论时间消耗如何。
  4.   
  5. 必须简单易用。
  6.   

答案::

  

@See java.util.concurrent.Future.get()doc。

     

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并发运气好运。

答案 3 :(得分: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();

修改

(很抱歉编辑这篇文章,但这段代码的竞争状况非常糟糕,我没有足够的声誉来评论)

如果您100%确定在await()之后调用signal(),则只能使用此方法。这就是为什么你不能使用像Java这样的Java对象的一个​​重要原因。 Windows事件。

如果代码按此顺序运行:

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

然后线程2将永远等待。这是因为Object.notify()只唤醒当前正在运行的线程之一。稍后等待的线程不会被唤醒。这与我期望事件工作的方式非常不同,事件发出信号直到a)等待或b)显式重置。

注意:大多数情况下,您应该使用notifyAll(),但这与上面的“永远等待”问题无关。

答案 4 :(得分:7)

java.util.concurrent包中提取CountDownLatch类,它提供了更高级别的同步机制,远比任何低级别的东西都更不容易出错。

答案 5 :(得分:6)

您可以使用两个线程之间共享的Exchanger对象来执行此操作:

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

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

在第二个帖子中:

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

}

正如其他人所说,不要采取这种轻松的,只是复制粘贴代码。先做一些阅读。

答案 6 :(得分:6)

很多正确的答案,但没有一个简单的例子..这是一种简单易懂的方法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

答案 7 :(得分:4)

java.lang.concurrent包中的Future接口旨在提供对在另一个帖子中计算的结果的访问权。

查看FutureTaskExecutorService,了解现成的做法。

我强烈建议任何对并发和多线程感兴趣的人阅读Java Concurrency In Practice。它显然集中在Java上,但是对于任何使用其他语言的人来说都有很多东西。

答案 8 :(得分:2)

这适用于所有语言:

您希望拥有一个事件/侦听器模型。您创建一个等待特定事件的侦听器。该事件将在您的工作线程中创建(或发出信号)。这将阻止线程,直到接收到信号而不是不断轮询以查看是否满足条件,如您目前的解决方案。

您的情况是导致死锁的最常见原因之一 - 确保您发出其他线程的信号,而不管可能发生的错误。示例 - 如果您的应用程序抛出异常 - 并且从不调用该方法来通知另一个事情已完成。这将使其他线程永远不会“醒来”。

我建议您在实施案例之前,先了解使用事件和事件处理程序的概念,以便更好地理解这个范例。

或者,您可以使用互斥锁使用阻塞函数调用 - 这将导致线程等待资源空闲。要做到这一点,您需要良好的线程同步 - 例如:

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

答案 9 :(得分:2)

如果你想要快速和脏的东西,你可以在你的while循环中添加一个Thread.sleep()调用。如果数据库库是您无法更改的,那么实际上没有其他简单的解决方案。轮询数据库直到准备好等待时间不会导致性能下降。

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

几乎没有什么可以称为优雅代码,但完成工作。

如果您可以修改数据库代码,那么使用其他答案中建议的互斥锁会更好。

答案 10 :(得分:2)

您可以从一个线程中的阻塞队列中读取并在另一个线程中写入它。

答案 11 :(得分:1)

  1. join()已被排除
  2. 您已使用CountDownLatch
  3. Future.get()已由其他专家提出,
  4. 您可以考虑其他选择:

      来自ExecutorService

    1. invokeAll

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

      执行给定的任务,在完成所有任务后返回持有其状态和结果的Futures列表。

    2. 来自Executors
    3. ForkJoinPoolnewWorkStealingPool(自Java 8发布以来)

        

      使用所有可用处理器作为目标并行级别创建工作窃取线程池。

答案 12 :(得分:-1)

enter image description here

这个想法可以适用吗?如果您使用CountdownLatches或Semaphores工作完美,但如果您正在寻找面试的最简单答案,我认为这可以适用。