【问题标题】:How to make a Java thread wait for another thread's output?如何让 Java 线程等待另一个线程的输出?
【发布时间】:2010-09-22 07:28:41
【问题描述】:

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

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

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

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

还有其他想法吗?谢谢。

【问题讨论】:

    标签: java multithreading


    【解决方案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 包中的构造应该是要走的路。 stackoverflow.com/questions/289434/… 在我看来是目前最好的解决方案。
          • 正忙着等待,但是如果只是在这个特定的地方需要,而且如果无法访问db库,你还能做什么呢?忙碌的等待不一定是邪恶的
          【解决方案6】:

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

          查看FutureTaskExecutorService 了解执行此类操作的现成方法。

          我强烈建议任何对并发和多线程感兴趣的人阅读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/…stackoverflow.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】:

              需求::

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

              回答::

              @参见 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】:

                自从

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

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

                1. invokeAll 来自ExecutorService

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

                  执行给定的任务,当所有任务完成时返回一个包含状态和结果的 Futures 列表。

                2. ForkJoinPoolnewWorkStealingPool 来自 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
                  

                  【讨论】:

                    猜你喜欢
                    • 1970-01-01
                    • 1970-01-01
                    • 2011-07-10
                    • 2015-02-22
                    • 2017-09-28
                    • 1970-01-01
                    • 1970-01-01
                    • 2021-06-21
                    相关资源
                    最近更新 更多