2011-06-07 10 views
14

Un hilo puede utilizar Object.wait() para bloquear hasta que otro subproceso llama notify() o notifyAll() en ese objeto.Java: ¿cómo puede esperar un hilo en varios objetos?

Pero, ¿y si un hilo quiere esperar hasta que se señalice uno de los múltiples objetos? Por ejemplo, mi hilo debe esperar hasta que sea a) los bytes están disponibles para leer de un InputStream o b) se añade un elemento a una ArrayList.

¿Cómo puede la espera de rosca para cualquiera de estos eventos a ocurrir?

EDITAR

This question se ocupa de espera de varios subprocesos para completar - mi caso se trata de un hilo a la espera de uno de los muchos objetos que se singnaled.

+0

Tal vez puede publicar algo de código fuente, incluye la parte en que "espera" se llama y las dos partes en las que su notificar debe suceder. –

+1

* ¿Cómo puede un hilo esperar en múltiples objetos *, con cola? Bloquee el objeto, colóquelo en la cola en un extremo y desbloquéelo. El otro lado: sacarlo de otro, adquirir el bloqueo, procesar, liberar el bloqueo. Algo a lo largo de estas líneas. – bestsss

Respuesta

4

Un hilo no puede esperar en más de un objeto a la vez.

Los métodos wait() y notify() son específicos a objetos. El método wait() suspende el hilo de ejecución actual y le dice al objeto que realice un seguimiento del hilo suspendido. El método notify() le dice al objeto que active los hilos suspendidos de los que actualmente realiza un seguimiento.

Enlace de interés: Can a thread call wait() on two locks at once in Java (6) ?

1

Bloqueo en ambos casos sobre el mismo objeto. Llamar en caso de a) o en el caso b) notificar() sobre el mismo objeto.

5

Todos pueden usar el mismo mutex. Su consumidor está esperando en ese mutex, los otros notifican en ese mutex cuando puede proceder el primero.

+1

@Tim Büthe: No creo que esto funcione, porque cuando se le notifica, el hilo no podría distinguir entre la condición (a) y (b), es decir, no sabría si hay más bytes disponibles en el 'InputStream' o se ha agregado un elemento a 'ArrayList' – MarcoS

+0

@MarcoS Por supuesto, se puede distinguir, pero eso no forma parte de la sincronización. Puede mantener un estado, por ejemplo, en algún lugar, un mensaje o similar. –

+0

@MarcoS: Bueno, directo, simplemente podría establecer una bandera antes de notificar el hilo de espera ... –

19

de que estás en un mundo de dolor. Utilice una abstracción de nivel superior, como una cola de mensajes de bloqueo, desde la cual el hilo puede consumir mensajes como "más bytes disponibles" o "elemento agregado".

+0

así es como debería ser ... – bestsss

2

Puede esperar solo en un monitor. Entonces los notificadores deben notificar a este monitor. No hay otra forma en esta sincronización de bajo nivel.

2

Un poco tarde, pero es una pregunta muy interesante! Parece que puede esperar múltiples condiciones, con el mismo rendimiento y sin hilos adicionales; ¡Solo se trata de definir el problema! Me tomé el tiempo para escribir una explicación más detallada dentro de los commits del código de abajo. A pedido extraeré la abstracción:

De hecho, esperar en múltiples objetos es lo mismo que esperar en múltiples condiciones. Pero el siguiente paso es fusionar sus subcondiciones en una condición -net -una-condición individual-. Y cuando algún componente de la condición haga que se vuelva verdadero, se activa un booleano y se notifica al bloqueo (como cualquier otra condición de espera de notificación).

Mi enfoque:

para cualquier condición, sólo puede dar lugar a dos valores (verdadero y falso). Cómo se produce ese valor es irrelevante. En su caso, su "condición funcional" es cuando cualquiera de los dos valores es verdadero: (valor_a || valor_b). Llamo a esta "condición funcional" como el "Punto Nexus".Si aplica la perspectiva de que cualquier condición compleja, sin importar qué tan compleja sea, siempre produce un resultado simple (verdadero o falso), entonces lo que realmente está pidiendo es "¿Qué causará que mi condición neta se haga realidad?" (Suponiendo que la lógica es "Espera hasta que sea cierto"). Por lo tanto, cuando un hilo hace que un componente de su condición se vuelva verdadero (estableciendo value_a, o value_b en true, en su caso), y sabe que hará que se cumpla su condición de red deseada, entonces puede simplificar su enfoque a un clásico (en el sentido de que voltea un solo indicador booleano y libera un bloqueo). Con este concepto, se puede aplicar un enfoque objeto de coordenadas para ayudar a ayudar a la claridad de su lógica global:

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. 
     } 

    } 


} 
+2

Bienvenido, @Jamie. Estoy seguro de que contiene una gran respuesta, pero todavía no es una respuesta de StackExchange.Le sugiero que extraiga la descripción como su respuesta y que la ilustre con fragmentos del código, según sea necesario, en lugar de pedirle a los lectores que extraigan los bits relevantes del volcado del código. Por cierto, supongo que tienes el derecho de publicar este código aquí - ¿es correcto? Solo pregunto porque parece una clase tomada al por mayor de un proyecto o materiales del curso. –

+1

¡Gracias, y SÍ, lo hago! Es mi primer mensaje, y es una consolidación de aproximadamente una hora de enfoque puro (y algo de filosofía). Casi considero hacer una biblioteca para eso, pero es denso. Creo que vale la pena explicar, simplemente, el genio detrás de lo que parece ser un componente faltante en el JLS. ¡Intentaré comprimirlo! –

1

Parece ser que en el caso de que esté a la espera de "notificaciones" de dos fuentes diferentes. Puede que no tenga que "esperar" (como en el caso normal de Java synchronized(object) object.wait()) en esos dos objetos per se, pero pídales a ambos que hablen o no (como las otras respuestas mencionan, alguna colección de bloqueo como LinkedBlockingQueue).

Si realmente desea "esperar" en dos objetos java diferentes, puede hacerlo aplicando algunos de los principios de esta respuesta: https://stackoverflow.com/a/31885029/32453 (básicamente, nueva cada thread para hacer una espera en cada uno de los objetos que está esperando, pídales que notifiquen el hilo principal cuando se le notifica al objeto mismo), pero puede que no sea fácil administrar los aspectos sincronizados.

0

Para manejar la terminación de cualquier hilo de un determinado conjunto de sin esperar a que todos ellos para terminar, un objeto común dedicado (lastExited abajo) se puede utilizar como monitor (wait() y notify() en synchronized bloques) . Se requieren monitores adicionales para garantizar que en cualquier momento esté saliendo un hilo como máximo (notifyExitMutex) y que como máximo un hilo esté esperando a que salga cualquier hilo (waitAnyExitMonitor); por lo tanto, los pares wait()/notify() pertenecen siempre a diferentes bloques.

Ejemplo (todas las terminaciones de procesos se manejan en el orden de los hilos terminó):

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--; 
     } 
    } 
} 
Cuestiones relacionadas