2010-05-14 16 views
10

Ho un Java Thread che espone una proprietà che altri thread vogliono accedere:Blocco chiamanti fino a quando getFoo() ha un valore pronto?

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

Il problema è che ci vuole un po 'poco tempo dal momento in cui questo viene eseguito fino a quando foo è disponibile. I chiamanti possono chiamare lo getFoo() prima e ottenere uno null. Preferirei semplicemente bloccare, attendere e ottenere il valore una volta che l'inizializzazione è avvenuta. (foo non viene mai modificato in seguito.) Sarà questione di millisecondi finché non sarà pronto, quindi sono a mio agio con questo approccio.

Ora, posso farlo accadere con wait() e notifyAll() e c'è il 95% di possibilità che lo farò bene. Ma mi chiedo come lo fareste tutti; c'è una primitiva in java.util.concurrent che farebbe questo, che mi sono perso?

Oppure, come lo strutturate? Sì, rendere foo volatile. Sì, sincronizza su un lucchetto interno Object e inserisci il check in un ciclo while finché non è null. Mi sto perdendo qualcosa?

risposta

14

Se foo viene inizializzato solo una volta, un CountDownLatch si adatta perfettamente.

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(); 
    } 

} 

Fermi forniscono lo stesso comportamento visibilità della parola volatile, il che significa che le discussioni lettura vedrà il valore di foo assegnato dal filo, anche se non è dichiarata foovolatile.

+0

sei veloce! LOL – Kiril

0

È possibile utilizzare il l'attesa() e notify() metodi:

semplice esempio qui:

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

+0

Sicuramente, posso rotolare una soluzione a mano. Penso che sia un po 'più complesso di questo - il campo dovrebbe essere' volatile 'e abbiamo bisogno di 'notifyAll()' per iniziare - quindi speravo in commenti su cos'altro ho bisogno, o una soluzione preconfezionata che è sicuramente destra. –

+0

@Sean In tal caso vorrei andare con il suggerimento di DJClayworth –

0

quanto ho capito, roba concomitante è stato creato in modo esplicito di non aspettare e fare quello che vuoi subito - ma se è possibile a tutti. Nel tuo caso devi aspettare che qualcosa sia disponibile, quindi la tua unica opzione è, uh, a wait(). In breve, sembra che il modo in cui l'hai descritto sia l'unico modo corretto.

3

In generale, notificare() e notifyAll() sono i metodi desiderati. notify() è pericoloso se solo un elemento sta creando Foo e molti thread potrebbero attendere. Ma penso che ci siano altri problemi qui.

Non vorrei rendere il filo il luogo in cui riporre il Foo. Ciò significa che devi mantenere un filo in giro dopo che Foo è stato creato. Perché non creare un altro oggetto per memorizzare Foo e scrivere il thread di creazione?

Quindi avrei getFoo() test foo e attendo solo se non era nullo (non dimenticare di sincronizzarlo con se stesso e con il foo setter).

+0

+1 Per estrarre foo da MyThread –

+0

D'accordo, vorrei che non fosse così. In questo caso devo davvero creare l'oggetto all'interno di run(). È un'app GUI con esigenze strane. –

0

L'inizializzazione lazy è un'opzione?

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

Non in questo caso poiché è necessario eseguire foo nell'altro thread. –

0

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

Non credo wait/notifyAll funzionerà, perché ogni wait si aspettano un notify. Vuoi avvisare una volta e poi non preoccuparti mai più con la notifica, quindi qualsiasi altro thread che sta chiamando getFoo bloccherà fino a quando foo viene inizializzato o semplicemente ottieni foo se è già inizializzato.

+0

In realtà, c'è un piccolo bug qui: poiché 'latch' non è' final' o 'volatile', non è garantito che sia" visibile "ad altri thread. Potrebbe non essere un problema nella pratica, ma la garanzia non costa nulla. – erickson

1

userei una delle 's BlockingQueue in java.util.concurrent In particolare, se c'è un thread in attesa di Foo e uno produrlo userei un SynchronousQueue nei casi di più produttori e/o più consumatori mia opzione predefinita è un LinkedBlockingQueue, ma altre implementazioni potrebbero essere più adatte alla tua applicazione. Il tuo codice diventa quindi:

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 è un costrutto utile in molti casi, ma qui non si adatta molto bene al requisito: 'pippo' è solo inizializzato una volta e letto probabilmente molte volte. Questo permetterebbe di leggere 'foo' solo una volta. Altri tentativi di leggere la proprietà si bloccerebbero per sempre. – erickson

+0

Sì, hai ragione, mi era mancata la parte "solo inizializzato una volta". Pensavo che si trattasse più di una situazione produttore/consumatore. –

0

magari provare la mia classe FutureValue fantasia ...

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(); 
} 
} 
Problemi correlati