【问题标题】:Java: How can a thread wait on multiple objects?Java:一个线程如何等待多个对象?
【发布时间】:2011-09-10 01:10:55
【问题描述】:

一个线程可以使用Object.wait() 进行阻塞,直到另一个线程对该对象调用notify()notifyAll()

但是如果一个线程想要等到多个对象中的一个发出信号怎么办?例如,我的线程必须等到 either a) 字节变得可用于从 InputStream 读取或 b) 将项目添加到 ArrayList

线程如何等待这些事件中的任何一个发生?

编辑

This question 处理等待多个线程完成的问题——我的案例涉及一个线程等待多个对象之一被信号化。

【问题讨论】:

  • 也许您可以发布一些源代码,包括调用“等待”的部分以及应该发生通知的两个部分。
  • 线程如何等待多个对象,有队列。锁定对象,放入队列一端并解锁。对方:从对方中弹出,获取锁,处理,释放锁。类似的东西。

标签: java multithreading wait notify


【解决方案1】:

你正处于一个痛苦的世界。使用更高级别的抽象,例如阻塞消息队列,线程可以从中使用消息,例如“更多可用字节”或“已添加项目”。

【讨论】:

    【解决方案2】:

    他们都可以使用相同的互斥锁。您的消费者正在等待该互斥体,当第一个可以继续时,其他两个都在该互斥体上通知。

    【讨论】:

    • @Tim Büthe:我认为这行不通,因为当收到通知时,线程将无法区分条件(a)和(b),即它不知道是否有更多字节在InputStream 中可用或已将项目添加到ArrayList
    • @MarcoS 当然,它可以区分,但这不是同步的一部分。例如,您可以在某处保存状态、消息或类似内容。
    • @MarcoS:嗯,直截了当,您可以在通知等待线程之前设置一些标志...
    • @Fatal: 是的,不过我更喜欢this solution
    • @MarcoS 这正是这样的解决方案。阻塞队列是您可以锁定的单个互斥体,您从中获得的消息是我们在这里讨论的额外信息。不过,我对这两个答案都投了赞成票 :-)
    【解决方案3】:

    一个线程一次不能等待多个对象。

    wait()notify() 方法是特定于对象的。 wait() 方法挂起当前的执行线程,并告诉对象跟踪挂起的线程。 notify() 方法告诉对象唤醒它当前正在跟踪的挂起线程。

    有用的链接:Can a thread call wait() on two locks at once in Java (6) ?

    【讨论】:

      【解决方案4】:

      有点晚了,但这是一个非常有趣的问题! 看起来您确实可以等待多个条件,具有相同的性能,并且没有额外的线程;这只是定义问题的问题!我花时间在下面的代码提交中写了更详细的解释。根据要求,我将提取抽象:

      所以实际上等待多个对象,与等待多个条件相同。但下一步是将您的子条件合并为-net-条件-单个条件-。当条件的任何组成部分使其变为真时,您翻转一个布尔值,并通知锁(就像任何其他等待通知条件一样)。

      我的方法

      对于任何条件,它只能产生两个值(真和假)。该价值如何产生无关紧要。在您的情况下,您的“功能条件”是两个值之一为真时:(value_a || value_b)。我将这种“功能条件”称为“Nexus-Point”。如果您应用任何复杂条件(无论多么复杂)总是产生简单结果(真或假)的观点,那么您真正要求的是“什么会导致我的净条件变为真?” (假设逻辑是“等到真”)。因此,当一个线程导致你的条件的一个组件变为真(在你的情况下将 value_a 或 value_b 设置为真),并且你知道它会导致你想要的 -net- 条件得到满足,那么你可以简化你的一种经典的方法(因为它翻转一个布尔标志,并释放一个锁)。有了这个概念,您可以应用对象纵坐标方法来帮助您明确整体逻辑:

      import java.util.HashSet;
      import java.util.Set;
      
      /**
       * The concept is that all control flow operation converge
       * to a single value: true or false. In the case of N
       * components in which create the resulting value, the
       * theory is the same. So I believe this is a matter of
       * perspective and permitting 'simple complexity'. for example:
       *
       * given the statement:
       *      while(condition_a || condition_b || ...) { ... }
       *
       * you could think of it as:
       *      let C = the boolean -resulting- value of (condition_a || condition_b || ...),
       *      so C = (condition_a || condition_b || ...);
       *
       * Now if we were to we-write the statement, in lamest-terms:
       *      while(C) { ... }
       *
       * Now if you recognise this form, you'll notice its just the standard
       * syntax for any control-flow statement?
       *
       *      while(condition_is_not_met) {
       *          synchronized (lock_for_condition) {
       *              lock_for_condition.wait();
       *            }
       *      }
       *
       * So in theory, even if the said condition was evolved from some
       * complex form, it should be treated as nothing more then if it
       * was in the simplest form. So whenever a component of the condition,
       * in which cause the net-condition (resulting value of the complex
       * condition) to be met, you would simply flip the boolean and notify
       * a lock to un-park whoever is waiting on it. Just like any standard
       * fashion.
       *
       * So thinking ahead, if you were to think of your given condition as a
       * function whos result is true or false, and takes the parameters of the states
       * in which its comprised of (  f(...) = (state_a || state_b && state_c), for example )
       * then you would recognize "If I enter this state, in which this I know would
       * cause that condition/lock to become true, I should just flip the switch switch,
       * and notify".
       *
       * So in your example, your 'functional condition' is:
       *      while(!state_a && !state_b) {
       *          wait until state a or state b is false ....
       *      }
       *
       * So armed with this mindset, using a simple/assertive form,
       * you would recognize that the overall question:
       * -> What would cause my condition to be true? : if  state_a is true OR state_b is true
       * Ok... So, that means: When state_a or state_b turn true, my overall condition is met!
       * So... I can just simplify this thing:
       *
       *      boolean net_condition = ...
       *      final Object lock = new Lock();
       *
       *      void await() {
       *          synchronized(lock) {
       *              while(!net_condition) {
       *                  lock.wait();
       *              }
       *           }
       *       }
       *
       * Almighty, so whenever I turn state_a true, I should just flip and notify
       * the net_condition!
       *
       *
       *
       * Now for a more expanded form of the SAME THING, just more direct and clear:
       *
       * @author Jamie Meisch
       */
      public class Main {
      
      
          /**
           *
           * The equivalent if one was to "Wait for one of many condition/lock to
           * be notify me when met" :
           *
           *      synchronized(lock_a,lock_b,lock_c) {
           *          while(!condition_a || !condition_b || !condition_c) {
           *              condition_a.wait();
           *              condition_b.wait();
           *              condition_c.wait();
           *          }
           *      }
           *
           */
          public static void main(String... args) {
      
              OrNexusLock lock = new OrNexusLock();
              // The workers register themselves as their own variable as part of the overall condition,
              // in which is defined by the OrNuxusLock custom-implement. Which will be true if any of
              // the given variables are true
              SpinningWarrior warrior_a = new SpinningWarrior(lock,1000,5);
              SpinningWarrior warrior_b = new SpinningWarrior(lock,1000,20);
              SpinningWarrior warrior_c = new SpinningWarrior(lock,1000,50);
      
              new Thread(warrior_a).start();
              new Thread(warrior_b).start();
              new Thread(warrior_c).start();
      
              // So... if any one of these guys reaches 1000, stop waiting:
              // ^ As defined by our implement within the OrNexusLock
      
      
              try {
                  System.out.println("Waiting for one of these guys to be done, or two, or all! does not matter, whoever comes first");
                  lock.await();
                  System.out.println("WIN: " + warrior_a.value() + ":" + warrior_b.value() + ":" + warrior_c.value());
              } catch (InterruptedException ignored) {
              }
      
          }
      
      
          // For those not using Java 8 :)
          public interface Condition {
              boolean value();
          }
      
          /**
           * A variable in which the net locks 'condition function'
           * uses to determine its overall -net- state.
           */
          public static class Variable {
      
              private final Object lock;
              private final Condition con;
      
              private Variable(Object lock, Condition con) {
                  this.lock = lock;
                  this.con  = con;
              }
      
              public boolean value() {
                  return con.value();
              }
      
              //When the value of the condition changes, this should be called
              public void valueChanged() {
                  synchronized (lock) {
                      lock.notifyAll();
                  }
              }
      
          }
      
      
      
          /**
           *
           * The lock has a custom function in which it derives its resulting
           * -overall- state (met, or not met). The form of the function does
           * not matter, but it only has boolean variables to work from. The
           * conditions are in their abstract form (a boolean value, how ever
           * that sub-condition is met). It's important to retain the theory
           * that complex conditions yeild a simple result. So expressing a
           * complex statement such as ( field * 5 > 20 ) results in a simple
           * true or false value condition/variable is what this approach is
           * about. Also by centerializing the overal logic, its much more
           * clear then the raw -simplest- form (listed above), and just
           * as fast!
           */
          public static abstract class NexusLock {
              private final Object lock;
      
              public NexusLock() {
                  lock = new Object();
              }
      
              //Any complex condition you can fathom!
              //Plus I prefer it be consolidated into a nexus point,
              // and not asserted by assertive wake-ups
              protected abstract boolean stateFunction();
      
              protected Variable newVariable(Condition condition) {
                  return new Variable(lock, condition);
              }
      
              //Wait for the overall condition to be met
              public void await() throws InterruptedException {
                  synchronized (lock) {
                      while (!stateFunction()) {
                          lock.wait();
                      }
                  }
              }
      
          }
      
          // A implement in which any variable must be true
          public static class OrNexusLock extends NexusLock {
      
      
              private final Set<Variable> vars = new HashSet<>();
      
              public OrNexusLock() {
              }
      
      
              public Variable newVar(Condition con) {
                  Variable var = newVariable(con);
                  vars.add(var); //register it as a general component of or net condition       // We should notify the thread since our functional-condition has changed/evolved:
                  synchronized (lock) { lock.notifyAll(); }
                  return var;
              }
      
              @Override
              public boolean stateFunction() { //Our condition for this lock
                  // if any variable is true: if(var_a || var_b || var_c || ...)
      
                  for(Variable var : vars) {
                      if(var.value() == true) return true;
                  }
                  return false;
              }
      
          }
      
          //increments a value with delay, the condition is met when the provided count is reached
          private static class SpinningWarrior implements Runnable, Condition {
      
              private final int count;
              private final long delay;
              private final Variable var;
      
              private int tick = 0;
      
              public SpinningWarrior(OrNexusLock lock, int count, long delay) {
                  this.var   = lock.newVar(this);
                  this.count = count; //What to count to?
                  this.delay = delay;
              }
      
              @Override
              public void run() {
                  while (state_value==false) { //We're still counting up!
                      tick++;
                      chkState();
                      try {
                          Thread.sleep(delay);
                      } catch (InterruptedException ignored) {
                          break;
                      }
                  }
              }
      
              /**
               * Though redundant value-change-notification are OK,
               * its best to prevent them. As such its made clear to
               * that we will ever change state once.
               */
              private boolean state_value = false;
              private void chkState() {
                  if(state_value ==true) return;
                  if(tick >= count) {
                      state_value = true;
                      var.valueChanged(); //Our value has changed
                  }
              }
      
              @Override
              public boolean value() {
                  return state_value; //We could compute our condition in here, but for example sake.
              }
      
          }
      
      
      }
      

      【讨论】:

      • 欢迎您,@Jamie。我确信这包含一个很好的答案,但它还不是 StackExchange 的答案。我建议您将描述提取为您的答案,并根据需要使用代码的 sn-ps 进行说明,而不是要求读者从您的代码转储中挖掘相关位。顺便说一句,我假设您有权在此处发布此代码 - 对吗?我之所以问,是因为它看起来像是从项目或课程材料中批发的课程。
      • 谢谢,是的,我愿意!这是我的第一篇文章,它融合了大约一个小时的纯粹专注(和一些哲学)。我几乎考虑为它制作一个库,但它太密集了。我相信它值得解释 - 简单地 - JLS 中似乎缺少的组件背后的天才。我会尽量压缩它!
      【解决方案5】:

      在这两种情况下都锁定在同一个对象上。在同一个对象上调用 case a) 或 case b) notify()。

      【讨论】:

        【解决方案6】:

        您只能在一台显示器上等待。所以通知者必须通知这台监视器。在这种低级同步中没有其他方法。

        【讨论】:

          【解决方案7】:

          在您的情况下,您似乎正在等待来自两个不同来源的“通知”。您可能不必在这两个对象本身上“等待”(如在普通 java synchronized(object) object.wait() 中),但让它们都与队列或其他对象交谈(正如其他答案所提到的,一些阻塞集合,如 LinkedBlockingQueue)。

          如果你真的想在两个不同的 java 对象上“等待”,你可以通过应用这个答案中的一些原则来做到这一点:https://stackoverflow.com/a/31885029/32453(基本上每个线程都新建一个线程来等待每个在您等待的对象中,让它们在通知对象本身时通知主线程),但管理同步方面可能并不容易。

          【讨论】:

            【解决方案8】:

            为了处理给定集合中任何线程的终止无需等待所有线程完成,可以使用专用的公共对象(lastExited 下面)用作监视器(wait()notify()synchronized 块中)。需要更多的监视器来确保在任何时候最多有一个线程退出(notifyExitMutex),并且最多有一个线程正在等待任何线程退出(waitAnyExitMonitor);因此wait()/notify() 对总是属于不同的块。

            示例(所有进程终止都按线程完成的顺序处理):

            import java.util.Random;
            
            public class ThreadMonitor {
            
                private final Runnable[] lastExited = { null };
            
                private final Object notifyExitMutex = new Object();
                public void startThread(final Runnable runnable) {
                    (new Thread(new Runnable() { public void run() {
                        try { runnable.run(); } catch (Throwable t) { }
                        synchronized (notifyExitMutex) {
                            synchronized (lastExited) {
                                while (true) {
                                    try {
                                        if (lastExited[0] != null) lastExited.wait();
                                        lastExited[0] = runnable;
                                        lastExited.notify();
                                        return;
                                    }
                                    catch (InterruptedException e) { }
                                }
                            }
                        }
                    }})).start();
                }
            
                private final Object waitAnyExitMutex = new Object();
                public Runnable waitAnyExit() throws InterruptedException {
                    synchronized (waitAnyExitMutex) {
                        synchronized (lastExited) {
                            if (lastExited[0] == null) lastExited.wait();
                            Runnable runnable = lastExited[0];
                            lastExited[0] = null;
                            lastExited.notify();
                            return runnable;
                        }
                    }
                }
            
                private static Random random = new Random();
                public static void main(String[] args) throws InterruptedException {
                    ThreadMonitor threadMonitor = new ThreadMonitor();
            
                    int threadCount = 0;
                    while (threadCount != 100) {
                        Runnable runnable = new Runnable() { public void run() {
                            try { Thread.sleep(1000 + random.nextInt(100)); }
                            catch (InterruptedException e) { }
                        }};
                        threadMonitor.startThread(runnable);
                        System.err.println(runnable + " started");
                        threadCount++;
                    }
            
                    while (threadCount != 0) {
                        Runnable runnable = threadMonitor.waitAnyExit();
                        System.err.println(runnable + " exited");
                        threadCount--;
                    }
                }
            }
            

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2020-10-04
              • 1970-01-01
              • 2016-05-01
              • 2011-07-10
              • 2011-07-21
              • 1970-01-01
              • 1970-01-01
              • 1970-01-01
              相关资源
              最近更新 更多