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


128

我正在用一个应用程序逻辑线程和一个数据库访问线程来制作Java应用程序。他们都坚持为应用程序和都需要的整个生命周期,以在同一时间运行(一个会谈到服务器,一个谈判给用户;当应用程序完全启动,我需要两个人工作)。

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

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

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

还有其他想法吗?谢谢。

Answers:


128

我真的建议您阅读Sun的Java Concurrency之类的教程。在进入神奇的多线程世界之前,先。

也有许多不错的书(有关“ Java并行编程”,“ Java并发实践”的Google书籍)。

为了得到你的答案:

在必须等待的代码中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

@ user1914692:不确定使用上述方法会有什么陷阱-需要进一步说明吗?
Piskvor于

1
@Piskvor:对不起,我很久以前写了这篇文章,但我几乎忘记了我的想法。也许我只是想更好地使用锁,而不是对象同步,后者是前者的一种简化形式。
user1914692 2014年

我不明白这是怎么回事。如果线程a在对象中等待,synchronised(object)另一个线程如何通过synchronized(object)调用object.notifyAll()?在我的程序中,一切都陷入了困境synchronozed
托马什Zato -恢复莫妮卡

@TomášZato第一个线程调用object.wait()有效地解除了对该对象的锁定。当第二个线程“退出”其同步块时,其他对象将从wait方法中释放,并在该点重新获得锁。
rogerdpack

140

使用CountDownLatch的计数器为1。

CountDownLatch latch = new CountDownLatch(1);

现在在应用程序线程中执行-

latch.await();

在db线程中,完成后,请-

latch.countDown();

4
我很喜欢这种解决方案,因为它很简单,尽管乍一看可能很难理解代码的含义。
lethal-guitar

3
这种用法要求您在闩锁用完时重新制作闩锁。要获得类似于可等待事件在Windows中使用,你应该尝试BooleanLatch,或复位CountDownLatch: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/... stackoverflow.com/questions / 6595835 /…
phyatt 2014年

1
嗨,如果我首先调用一个异步方法,它应该触发这样的事件:1)asyncFunc(); 2)latch.await(); 然后,一旦收到事件处理功能,我就将其倒计时。如何确保在调用latch.await()之前不会处理该事件?我想防止在第1行和第2行之间抢占。谢谢。
NioX5199

1
为了避免在出错时永远等待,请放countDown()一个finally{}大块
Daniel Alder

23

要求::

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

回答::

@See 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,但是您的方法要灵活得多。
皮斯克沃离开了建筑物

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();

编辑:

(很抱歉编辑这篇文章,但是这段代码的比赛条件非常糟糕,我没有足够的声誉来发表评论)

仅当您100%确定await()之后调用了signal()时,才可以使用此方法。这是为什么您不能使用Java对象(例如Windows Events)的一个重要原因。

如果代码按以下顺序运行:

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

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

注意:通常,您应该使用notifyAll(),但这与上面的“永远等待”问题无关。


7

尝试从包中取出CountDownLatch类,该类java.util.concurrent提供了更高级别的同步机制,与任何低级别的东西相比,它们出错的可能性要小得多。


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

}

正如其他人所说的那样,请不要随意使用此代码,而只能复制粘贴代码。首先阅读。


4

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

看一下FutureTaskExecutorService,了解一种现成的方法来做这种事情。

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


2

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

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

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

如果可以修改数据库代码,则最好使用其他答案中建议的互斥锁。


3
这几乎只是在等待。使用Java 5的util.concurrent包中的构造应该是方法。stackoverflow.com/questions/289434/…在我看来,这是目前最好的解决方案。
Cem Catikkas,

它正在等待,但是如果仅在此特定位置有此需要,并且如果无法访问数据库库,那么您还能做什么?忙碌的等待不一定是邪恶的
MarioOrtegón08年

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

2

您可以从一个线程中的阻塞队列中读取数据,然后在另一个线程中对其进行写入。


1

以来

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

您可以考虑其他替代方法:

  1. 来自的invokeAllExecutorService

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

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

  2. ForkJoinPoolnewWorkStealingPoolExecutors(从Java 8日发布)

    使用所有可用处理器作为目标并行度,创建窃取线程的池。


-1

在此处输入图片说明

这个想法可以适用吗?如果您使用CountdownLatches或Semaphores效果很好,但是如果您正在寻找面试的最简单答案,我认为这可能适用。


1
面试如何,但实际代码如何?
Piskvor

因为在这种情况下,顺序运行是一个等待另一个。最好的解决方案是使用信号量,因为使用CountdownLatches是这里给出的最佳答案,线程永不休眠,这意味着要使用CPU周期。
佛朗哥

但是重点并不是“按顺序运行它们”。我将编辑问题以使其更加清楚:GUI线程等待直到数据库准备就绪,然后在应用程序的其余执行过程中同时运行:GUI线程将命令发送到DB线程并读取结果。(再说一次:可以在面试中使用的代码点是什么,但是在实际代码中不能使用?我遇到的大多数技术面试官都具有代码背景,并且会问相同的问题;另外,我在实际的应用程序中需要使用此东西。我当时是在写书,不是为了烦琐的作业)
Piskvor在

1
所以。这是使用信号量的生产者消费者问题。我将尝试举一个例子
佛朗哥(Franco)

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.