如何让 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 并发好运。

【讨论】:

感谢您的回答!我用过CountdownLatches,但你的方法要灵活得多。【参考方案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 线程等待另一个线程的输出?的主要内容,如果未能解决你的问题,请参考以下文章

面试官:如何让主线程等待所有的子线程结束之后再执行?我懵了

C# 如何让线程等待到执行完毕才执行下一个循环

JAVA多线程中,如何让一个线程去等待N个线程执行完成后,再执行。

线程 等待/通知机制

我想知道JAVA多线程中,如何让一个线程去等待N个线程执行完成后,再执行。

MFC主线程等待另一个线程结束