2010-05-14 10 views
10

que tienen una aplicación Java Thread que expone una propiedad que otros hilos quieren acceso:Las llamadas bloquean hasta que getFoo() tenga un valor listo?

class MyThread extends Thread { 
    private Foo foo; 
    ... 
    Foo getFoo() { 
    return foo; 
    } 
    ... 
    public void run() { 
    ... 
    foo = makeTheFoo(); 
    ... 
    } 
} 

El problema es que se necesita algo de tiempo corto desde el momento presente se prolongará hasta foo está disponible. Las personas que llaman pueden llamar al getFoo() antes de esto y obtener un null. Prefiero que simplemente bloqueen, esperen y obtengan el valor una vez que se ha producido la inicialización. (foo nunca se cambia después). Pasará una cuestión de milisegundos hasta que esté listo, por lo que me siento cómodo con este enfoque.

Ahora, puedo hacer que esto ocurra con wait() y notifyAll() y hay una probabilidad del 95% que lo haré bien. Pero me pregunto cómo lo harían todos ustedes; ¿Hay algún primitivo en java.util.concurrent que haga esto, que me he perdido?

O, ¿cómo lo estructuraría? Sí, haga que foo sea volátil. Sí, sincronícelo en un bloqueo interno Object y ponga el cheque en un bucle while hasta que no sea null. ¿Me estoy perdiendo algo?

Respuesta

14

Si foo se inicializa solo una vez, un CountDownLatch encaja perfectamente.

class MyThread extends Thread { 

    private final CountDownLatch latch = new CountDownLatch(1); 

    ... 

    Foo getFoo() throws InterruptedException 
    { 
    latch.await(); /* Or use overload with timeout parameter. */ 
    return foo; 
    } 

    @Override 
    public void run() { 
    foo = makeTheFoo() 
    latch.countDown(); 
    } 

} 

Cierres proporcionan el mismo comportamiento visibilidad como la palabra clave volatile, lo que significa que las discusiones de lectura verán el valor de foo asignado por el hilo, a pesar de que no se ha declarado foovolatile.

+0

¡eres rápido! LOL – Kiril

0

Se puede utilizar el de la espera() y notify() métodos:

ejemplo sencillo aquí:

http://www.java-samples.com/showtutorial.php?tutorialid=306

+0

Definitivamente, puedo rodar una solución a mano. Creo que es un poco más complejo que esto: el campo debe ser 'volátil' y necesitamos' notifyAll() 'para comenzar, así que esperaba comentarios sobre qué más necesito, o una solución preempaquetada que definitivamente derecho. –

+0

@Sean En ese caso iría con la sugerencia de DJClayworth –

0

Según entiendo, cosas concurrente se crea de forma explícita no esperar y hacer lo quieres de inmediato, pero si es posible en absoluto. En su caso, debe esperar hasta que haya algo disponible, por lo que su única opción es, eh, al wait(). En resumen, parece que la forma en que lo describió es la única forma correcta.

3

En general, notify() y notifyAll() son los métodos que desea. notify() es peligroso si solo un elemento está creando el Foo y muchos hilos pueden esperarlo. Pero creo que hay algunos otros problemas aquí.

No haré que el hilo sea el lugar para almacenar el Foo. Hacerlo significa que tienes que mantener un hilo después de que se crea Foo. ¿Por qué no crear otro objeto para almacenar el Foo y hacer que el hilo de escritura cree en él?

Entonces tendría getFoo() prueba foo y solo esperaría si no fuera nulo (no olvide sincronizarlo consigo mismo y con el foo setter).

+0

+1 Para extraer foo de MyThread –

+0

Estoy de acuerdo, ojalá no fuera así. En este caso, realmente tengo que hacer el objeto dentro de run(). Es una aplicación GUI con necesidades raras. –

0

¿La inicialización lenta es una opción?

synchronized Foo getFoo() { 
    if (foo == null) 
     foo = makeFoo(); 
    } 
    return foo; 
} 
+0

No en este caso, ya que el foo debe estar en el otro hilo. –

0

Prueba el CountDownLatch:

class MyThread extends Thread { 
    private volatile CountDownLatch latch; 
    private Foo foo; 
    MyThread(){ 
     latch = new CountDownLatch(1); 
    } 
    ... 
    Foo getFoo() { 
    latch.await(); // waits until foo is ready 
    return foo; 
    } 
    ... 
    public void run() { 
    ... 
    foo = makeTheFoo(); 
    latch.countDown();// signals that foo is ready 
    ... 
    } 
} 

No creo wait/notifyAll va a funcionar, porque cada wait se espera una notify. Desea notificar una vez y luego no volver a molestarse con la notificación, entonces cualquier otro hilo que llame al getFoo bloqueará hasta que se inicialice foo o simplemente obtenga foo si ya está inicializado.

+0

En realidad, aquí hay un error sutil: como 'latch' no es' final' o 'volátil', no está garantizado que sea" visible "para otros hilos. Puede que no sea un problema en la práctica, pero la garantía en realidad no cuesta nada. – erickson

1

que haría uso de cualquiera de las BlockingQueue 's en java.util.concurrent Más específicamente, si hay un hilo en espera de Foo y uno producirlo que haría uso de un SynchronousQueue en los casos de más productores y/o consumidores más mi opción por defecto es un LinkedBlockingQueue, pero otras implementaciones pueden ser más adecuadas para su aplicación. Su código se convierte entonces en:

class MyThread extends Thread { 
    private SynchronousQueue<Foo> queue = new SynchronousQueue<Foo>(); 
    ... 
    Foo getFoo() { 
    Foo foo; 
    try { 
     foo = queue.take(); 
    } 
    catch (InteruptedException ex) { 
     ...stuff ... 
    } 
    return foo; 
    } 
    ... 
    public void run() { 
    ... 
    foo = makeTheFoo(); 
    try { 
     queue.put(foo); 
    } 
    catch (InteruptedException ex) { 
     ...stuff ... 
    } 
    ... 
    } 
} 
+0

SynchronousQueue es una construcción práctica en muchos casos, pero no encaja muy bien en el requisito: 'foo' solo se inicializa una vez y se lee posiblemente muchas veces. Esto solo permitiría leer 'foo' una vez. Otros intentos de leer la propiedad bloquearían para siempre. – erickson

+0

Sí, tiene razón, me había perdido la parte "solo inicializado una vez". Pensé que esto era más una situación de productor/consumidor. –

0

quizás tratar mi clase FutureValue de fantasía ...

import java.util.concurrent.CountDownLatch; 

public class FutureValue<T> { 
private CountDownLatch latch = new CountDownLatch(1); 
private T value; 

public void set(T value) throws InterruptedException, IllegalStateException { 
    if (latch.getCount() == 0) { 
     throw new IllegalStateException("Value has been already set."); 
    } 
    latch.countDown(); 
    this.value = value; 
} 

/** 
* Returns the value stored in this container. Waits if value is not available. 
* 
* @return 
* @throws InterruptedException 
*/ 
public T get() throws InterruptedException { 
    latch.await(); 
    return value; 
} 

} 

// Usage example 
class MyExampleClass { 
@SuppressWarnings("unused") 
private static void usageExample() throws InterruptedException { 
    FutureValue<String> futureValue = new FutureValue<>(); 

    // the thread that will produce the value somewhere 
    new Thread(new Runnable() { 

     @Override 
     public void run() { 
      try { 
       futureValue.set("this is future"); 
      } catch (InterruptedException e) { 
       e.printStackTrace(); 
      } 
     } 
    }).run(); 

    String valueProducedSomewhereElse = futureValue.get(); 
} 
} 
Cuestiones relacionadas