如何让 Java 线程等待另一个线程的输出?
Posted
技术标签:
【中文标题】如何让 Java 线程等待另一个线程的输出?【英文标题】:How to make a Java thread wait for another thread's output? 【发布时间】:2010-09-22 07:28:41 【问题描述】:我正在制作一个带有应用程序逻辑线程和数据库访问线程的 Java 应用程序。 它们都在应用程序的整个生命周期中持续存在,并且都需要同时运行(一个与服务器对话,一个与用户对话;当应用程序完全启动时,我需要 两者 他们工作)。
但是,在启动时,我需要确保最初应用程序线程等待数据库线程准备好(当前通过轮询自定义方法dbthread.isReady()
确定)。
我不介意应用程序线程在数据库线程准备好之前阻塞。
Thread.join()
看起来不是一个解决方案 - db 线程仅在应用关闭时退出。
while (!dbthread.isReady())
有点工作,但空循环消耗大量处理器周期。
还有其他想法吗?谢谢。
【问题讨论】:
【参考方案1】:这适用于所有语言:
您想要一个事件/侦听器模型。您创建一个侦听器来等待特定事件。该事件将在您的工作线程中创建(或发出信号)。这将阻塞线程直到接收到信号,而不是像您目前拥有的解决方案那样不断轮询以查看是否满足条件。
您的情况是导致死锁的最常见原因之一——请确保您向其他线程发出信号,而不管可能发生的错误如何。示例 - 如果您的应用程序抛出异常 - 并且从不调用该方法来通知其他事情已经完成。这将使其他线程永远不会“醒来”。
我建议您在实施案例之前研究使用事件和事件处理程序的概念,以更好地理解此范例。
或者,您可以使用互斥锁来使用阻塞函数调用,这将导致线程等待资源空闲。为此,您需要良好的线程同步 - 例如:
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】:我真的建议您在开始进入神奇的多线程世界之前先阅读Sun's Java Concurrency 之类的教程。
还有很多好书出来了(google for "Concurrent Programming in 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
包和子包中有很多漂亮的并发类。你真的需要在网上找资料来了解并发,或者买一本好书。
【讨论】:
如果我没记错的话,也不是所有线程代码都可以很好地集成到对象中。所以我不认为使用对象同步是实现这个线程相关工作的好方法。 @user1914692:不确定使用上述方法有什么陷阱 - 需要进一步解释吗? @Piskvor:对不起,我很久以前写过这篇文章,我几乎忘记了我的想法。也许我的意思是更好地使用锁,而不是对象同步,后者是前者的一种简化形式。 我不明白这是如何工作的。如果线程a
正在等待synchronised(object)
中的对象,另一个线程如何通过synchronized(object)
调用object.notifyAll()
?在我的程序中,一切都卡在了synchronozed
块上。
@TomášZato 第一个线程调用object.wait()
有效地解锁了该对象上的锁。当第二个线程“退出”其同步块时,其他对象将从wait
方法中释放并重新获得该点的锁。【参考方案3】:
从java.util.concurrent
包中尝试CountDownLatch 类,它提供了更高级别的同步机制,比任何低级别的东西更不容易出错。
【讨论】:
【参考方案4】:您可以使用两个线程之间共享的 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)
正如其他人所说,不要接受这种轻松的复制粘贴代码。先读一读。
【讨论】:
【参考方案5】:如果你想要一些快速而肮脏的东西,你可以在你的 while 循环中添加一个 Thread.sleep() 调用。如果数据库库是您无法更改的,那么真的没有其他简单的解决方案。轮询数据库直到准备好等待一段时间不会影响性能。
while (!dbthread.isReady())
Thread.sleep(250);
几乎不能称之为优雅的代码,但可以完成工作。
如果您可以修改数据库代码,那么使用其他答案中建议的互斥锁会更好。
【讨论】:
这只是忙着等待。使用 Java 5 的 util.concurrent 包中的构造应该是要走的路。 ***.com/questions/289434/… 在我看来是目前最好的解决方案。 正忙着等待,但是如果只是在这个特定的地方需要,而且如果无法访问db库,你还能做什么呢?忙碌的等待不一定是邪恶的【参考方案6】:java.lang.concurrent
包中的Future 接口旨在提供对在另一个线程中计算的结果的访问。
查看FutureTask 和ExecutorService 了解执行此类操作的现成方法。
我强烈建议任何对并发和多线程感兴趣的人阅读Java Concurrency In Practice。它显然专注于 Java,但对于使用其他语言的人来说也有很多好处。
【讨论】:
【参考方案7】:使用计数器为 1 的 CountDownLatch。
CountDownLatch latch = new CountDownLatch(1);
现在在应用线程做-
latch.await();
在db线程中,完成后,做-
latch.countDown();
【讨论】:
我真的很喜欢这种简单的解决方案,尽管乍一看可能很难理解代码的含义。 这种用法需要您在闩锁用完后重新制作。要获得类似于 Windows 中的可等待事件的用法,您应该尝试使用 BooleanLatch 或可重置的 CountDownLatch:docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…***.com/questions/6595835/… 嗨,如果我首先调用它应该触发事件的异步方法: 1) asyncFunc(); 2)latch.await();然后,一旦收到,我就会在事件处理功能中倒计时。如何确保在调用 latch.await() 之前不会处理该事件?我想防止第 1 行和第 2 行之间的抢占。谢谢。 为避免出现错误时永远等待,请将countDown()
放入finally
块中【参考方案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 resultsReady = new ThreadEvent();
在等待结果的方法中:
resultsReady.await();
在创建所有结果后创建结果的方法中:
resultsReady.signal();
编辑:
(很抱歉编辑了这篇文章,但这段代码的竞争条件非常糟糕,我没有足够的声誉来发表评论)
只有当你 100% 确定在 await() 之后调用了 signal() 时,你才能使用它。这是您不能使用 Java 对象的一个重要原因,例如Windows 事件。
如果代码按此顺序运行:
Thread 1: resultsReady.signal();
Thread 2: resultsReady.await();
然后线程 2 将永远等待。这是因为 Object.notify() 只唤醒当前正在运行的线程之一。稍后等待的线程不会被唤醒。这与我期望事件的工作方式非常不同,在这种情况下,事件会发出信号,直到 a) 等待或 b) 显式重置。
注意:大多数情况下,您应该使用 notifyAll(),但这与上面的“永远等待”问题无关。
【讨论】:
【参考方案9】:需求::
等待下一个线程的执行直到上一个线程完成。 在前一个线程停止之前,下一个线程不得启动,与时间消耗无关。 必须简单易用。
回答::
@参见 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 并发好运。
【讨论】:
感谢您的回答!我用过CountdownLatch
es,但你的方法要灵活得多。【参考方案10】:
您可以在一个线程中读取阻塞队列,然后在另一个线程中写入。
【讨论】:
【参考方案11】:这个想法可以应用吗?如果您使用 CountdownLatches 或 Semaphores 效果很好,但如果您正在寻找最简单的面试答案,我认为这可以适用。
【讨论】:
这对于面试来说是怎么回事,但对于实际代码来说却不行? 因为在这种情况下是按顺序运行一个等待另一个。最好的解决方案可能是使用信号量,因为使用 CountdownLatches 是这里给出的最佳答案,线程永远不会进入睡眠状态,这意味着使用 CPU 周期。 但重点不是“按顺序运行它们”。我将编辑问题以使这一点更清楚:GUI 线程等待数据库准备好,然后在应用程序的其余部分执行同时运行:GUI 线程将命令发送到 DB 线程并读取结果。 (再说一遍:可以在面试中使用但不能在实际代码中使用的代码有什么意义?我遇到的大多数技术面试官都有代码背景并且会问同样的问题;另外,我需要这个东西用于实际的应用程序我当时正在写作,不是为了偷懒) 所以。这是使用信号量的生产者消费者问题。我会尝试做一个例子 我创建了一个项目github.com/francoj22/SemProducerConsumer/blob/master/src/com/…。它工作正常。【参考方案12】:自从
join()
已被排除
您已经使用CountDownLatch 和
Future.get() 已经由其他专家提出,
您可以考虑其他替代方案:
invokeAll 来自ExecutorService
invokeAll(Collection<? extends Callable<T>> tasks)
执行给定的任务,当所有任务完成时返回一个包含状态和结果的 Futures 列表。
ForkJoinPool 或 newWorkStealingPool 来自 Executors
(从 Java 8 版本开始)
使用所有可用处理器作为目标并行级别创建一个工作窃取线程池。
【讨论】:
【参考方案13】:很多正确答案,但没有一个简单的例子..这是一个简单易用的方法如何使用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
【讨论】:
以上是关于如何让 Java 线程等待另一个线程的输出?的主要内容,如果未能解决你的问题,请参考以下文章
JAVA多线程中,如何让一个线程去等待N个线程执行完成后,再执行。