2012-03-06 16 views
6

Estoy tratando de diseñar una clase como Code Kata para mí que tiene una propiedad de valor que se puede establecer, y la clase puede emitir instancias de ValueListener. La idea es que haya una instancia de ValueHolder con muchos subprocesos de cliente accediendo a ella simultáneamente. Cada subproceso de cliente ha solicitado un ValueWatcher y ha llamado a waitForValue().Notificar subprocesos cuando el contador cambia

Lo que realmente estoy luchando es qué condición debo usar en el ciclo while() para evitar notificaciones espurias (es decir, el valor no ha cambiado). Puedo ver que este diseño puede hacer posible que las instancias de ValueWatcher pierdan actualizaciones, pero estoy menos preocupado por eso en esta etapa.

¡Agradecería cualquier orientación que se ofrezca!

public class ValueHolder { 

    private int value = 0; 
    private final Object monitor = new Object(); 

    public void setValue(int value) { 
    synchronized(monitor) { 
     this.value = value; 
     monitor.notifyAll(); 
    } 
    } 

    ValueWatcher createChangeWatcher() { 
    return new ValueWatcher(); 
    } 

    private class ValueWatcher { 
    public int waitForValue() { 
     synchronized(monitor) { 
     while (==== ??? =====) { 
      monitor.wait(); 
      return value; 
     } 
     } 
    } 
    }  
} 
+0

¿Ves notificaciones falsas? Si solo el actualizador llama 'notify' o' notifyall', entonces no veo cómo sería. – Gray

+0

¿Podría ser que la llamada 'setValue' solo debería' notificar' a otros si el 'valor' realmente ha cambiado? En este momento podría actualizarse con el mismo valor y todos recibirán una notificación. ¿Es eso lo que quieres? – Gray

+0

No veo por qué hay una necesidad de un ciclo 'while' en absoluto. – biziclop

Respuesta

3

Problema interesante. Aquí hay una solución en la parte superior de mi cabeza. Tener un número de versión junto con el valor que se está cambiando. Cada vez que se actualiza el valor, el número de versión también se incrementa para que los objetos ValueWatcher puedan verificar si la versión se ha ejecutado, lo que significa que se ha producido un cambio.

Editar: inicialmente tenían una AtomicLong pero yo estoy robando la idea de un objeto envoltorio de @John Vint.

private final VersionValue versionValue = new VersionValue(); 

public void setValue(int value) { 
    synchronized (monitor) { 
     versionValue.value = value; 
     versionValue.version++; 
     monitor.notifyAll(); 
    } 
} 

private class ValueWatcher { 
    private long localVersion = 0; 
    public int waitForValue() { 
     synchronized (monitor) { 
      while (true) { 
       if (localVersion < versionValue.version) { 
        // NOTE: the value might have been set twice here 
        localVersion = versionValue.version; 
        return versionValue.value; 
       } 
       monitor.wait(); 
      } 
     } 
    } 
} 

private static class VersionValue { 
    int value; 
    long version; 
} 

También, aunque activaciones falsas son posibles es importante recordar que el texto:

invocan siempre espera dentro de un bucle que las pruebas para la enfermedad que se esperaban. No suponga que la interrupción fue para la condición particular que estaba esperando, o que la condición sigue siendo verdadera.

Más acerca de las condiciones de carrera y los modelos de productor/consumidor que los despertadores espurios. Ver my page here about that.

+0

Para hacer eso sin carreras, debería combinar el número de versión con el valor en un único tipo atómico (por ejemplo: 'AtomicLong versionedValue = value | (long) versión << 0xffff'. – ninjalj

+0

Oh, lo veo. Tengo que moverme mi sincronización fuera del tiempo. Derecha. Gracias @ninjalj. – Gray

+0

raza: obtienes localCounter para la actualización 'n' y devuelves' value' de la actualización 'n + 1'. La próxima vez obtienes' localCounter' para la actualización 'n + 1' y devuelve un 'valor' sin modificar – ninjalj

0
private class ValueWatcher { 
    private int oldValue = 0; 

    public int waitForValue() { 
    synchronized(monitor) { 
     while (value == oldValue) { 
     monitor.wait(); 
     } 
     oldValue = value 
     return oldValue; 
    } 
    } 
} 
+0

Hay una carrera aquí en caso de que el valor cambie y luego se cambie nuevamente. – Gray

+0

Eso tiene un problema obvio: si establecemos el valor en el valor existente (es decir, 'newVal == oldVal') perderemos una actualización. [Problema ABA] (http://en.wikipedia.org/wiki/ABA_problem) – Voo

+0

También creo que hay un error allí que causaría un ciclo infinito. Creo que quieres decir 'if (oldValue! = Value) {oldValue = value; break} ' – Gray

2

Lo único que realmente importa es si el valor cambió después de ingresar el método y su bloque sincronizado. Por lo tanto, tome las marcas de tiempo de la última vez que se modificó un valor y solo continúe cuando la última marca de tiempo actualizada> luego cuando ingresó.

private final StampedValue stamped = new StampedValue(); 

    public void setValue(int value) { 
     synchronized (monitor) { 
      this.stamped.value = value; 
      this.stamped.lastUpdated = System.currentTimeMillis(); 
      monitor.notifyAll(); 
     } 
    } 
    private class ValueWatcher { 

     public int waitForValue() { 
      synchronized(monitor) { 
       long enteredOn = System.currentTimeMillis();  
       while (enteredOn > stamped.lastUpdated) { 
        monitor.wait(); 
       } 
       return stamped.value; 
      } 
     } 
    } 
    private class StampedValue { 
     long lastUpdated = System.currentTimeMillis(); 
     int value; 
    } 
+0

Hay algunos problemas en el ciclo 'while' @John. Creo que quieres la vuelta afuera de eso ¿no? – Gray

+0

Además, la primera vez a través de él es probable que si el valor no se ha establecido, el 'waitForValue()' devolverá 0 justo al bate porque 'enteredOn' es ahora y' lastUpdated' es 0. – Gray

+0

@Gray Sí, copiar y pegar me falló –

0
public class ValueHolder { 

    private final Object monitor = new Object(); 
    private LinkedList<WeakReference<ValueWatcher>> waiters = new LinkedList<WeakReference<ValueWatcher>>(); 

    public void setValue(int value) { 
     synchronized (monitor) { 
      Iterator<WeakReference<ValueWatcher>> it = waiters.iterator(); 
      while (it.hasNext()) { 
       WeakReference<ValueWatcher> ref = it.next(); 
       if (ref.get() == null) 
        it.remove(); 
       else 
        ref.get().waitingList.add(value); 
      } 
      monitor.notifyAll(); 
     } 
    } 

    ValueWatcher createChangeWatcher() { 
     ValueWatcher ret = new ValueWatcher(); 
     synchronized(monitor) { 
      waiters.add(new WeakReference<ValueWatcher>(ret)); 
     } 
     return ret; 
    } 

    private class ValueWatcher { 

     private Queue<Integer> waitingList = new LinkedList<Integer>(); 

     public int waitForValue() { 
      synchronized (monitor) { 
       while (waitingList.isEmpty()) { 
        monitor.wait(); 
       } 
       return waitingList.poll(); 
      } 
     } 
    } 
} 

La idea es que no perder de vista que está esperando y cada observador tiene una cola de los valores que se han establecido desde la última invocación de waitForValue(). Esto elimina la necesidad de almacenar value en ValueHolder, lo cual es bueno ya que cuando se despierta ValueWatcher, eso podría haber cambiado varias veces. El inconveniente de este método es que, como pueden ver, la creación de nuevos observadores se bloqueará hasta que el monitor esté libre.

1

¿Qué pasa con cada oyente que tiene un BlockingQueue que le da a la secuencia de determinación de valor como parte de su registro como un oyente? Luego, cuando se cambia el valor, el hilo de configuración de valor simplemente se repite en cada una de esas colas, dándole el nuevo valor. Es posible que desee utilizar BlockingQueue.offer en ese bucle, de modo que si un hilo aún no está listo para recibir el nuevo valor, no detendrá que otros hilos lo reciban.

Puede que este no sea el enfoque más eficiente, pero es simple, y la estructura concurrente (es decir, la parte difícil) está bien probada y se mantiene para usted. Y tampoco es tan ineficiente.

+0

+1 Esta sería una versión simplificada de lo que hice. – biziclop

Cuestiones relacionadas