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

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

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

Thread.join()看起来不像一个解决方案-db 线程只在应用程序关闭时退出。

while (!dbthread.isReady()) {}算是可行的,但是空循环消耗了大量的处理器周期。

还有别的主意吗? 谢谢。

298464 次浏览

这适用于所有语文:

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

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

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

或者你可以使用一个使用互斥锁的阻塞函数调用-这将导致线程等待资源被释放。为此,您需要良好的线程同步,例如:

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

我真的建议您在开始多线程的神奇世界之前先学习一下像 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等。自 Java5以来,在 java.util.concurrent包和子包中有许多漂亮的并发类。您确实需要在线找到材料来了解并发性,或者获得一本好书。

尝试使用 java.util.concurrent包中的 CountDownLatch类,它提供了更高级别的同步机制,与任何低级别的同步机制相比,这些同步机制更不容易出错。

您可以使用两个线程之间共享的 交换器对象来完成:

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) {


}

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

如果您想要快速、简单的操作,只需在 while 循环中添加一个 Thread.sleep ()调用。如果数据库库是您无法更改的,那么实际上没有其他简单的解决方案。轮询数据库直到等待期结束并不会影响性能。

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

几乎不能称之为优雅的代码,但却可以完成工作。

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

java.lang.concurrent包中的 未来接口用于提供对在另一个线程中计算的结果的访问。

看看 未来任务执行服务,找到一种现成的方法来做这种事情。

我强烈建议对并发和多线程感兴趣的人阅读 实践中的 Java 并发。它显然专注于 Java,但是对于任何使用其他语言的人来说也有很多肉。

使用计数器为1的 CountDownLatch

CountDownLatch latch = new CountDownLatch(1);

现在在应用程序线程做-

latch.await();

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

latch.countDown();
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% 确定该信号()被调用时,才能使用此方法。这就是为什么你不能使用 Java 对象的一个重要原因,比如 Windows 事件。

If 代码按以下顺序运行:

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

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

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

要求:

  1. 等待下一个线程的执行,直到上一个线程完成。
  2. 下一个线程必须等到上一个线程停止后才能启动,不管时间消耗如何。
  3. 它必须简单易用。

答案:

@ See java.util.conture.Future.get () doc.

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

您可以在一个线程中读取阻塞队列,然后在另一个线程中写入该队列。

enter image description here

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

自从

  1. join()已经被排除了
  2. 你已经使用了 CountDownLatch
  3. Get () 已经由其他专家提出,

你可以考虑其他选择:

  1. ExecutorService调用 All

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

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

  2. 来自 Executors的 ForkJoinPool 偷窃工作池(自 Java 8发布以来)

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

很多正确的答案,但没有一个简单的例子。.下面是一个简单易行的使用 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