2011-12-19 14 views
12

Ho una discussione che aggiorna il suo stato di tanto in tanto e voglio che un secondo thread sia in grado di aspettare che il primo thread sia fatto. Qualcosa di simile a questo:In attesa di un evento in Java: quanto è difficile?

Thread 1: 
    while(true) { 
     ...do something... 
     foo.notifyAll() 
     ...wait for some condition that might never happen... 
     ... 
    } 

Thread 2: 
    ... 
    foo.wait(); 
    ... 

Ora, questo sembra bello e tutto meno che Thread 1 di notifyAll() viene eseguito prima attesa Thread 2 di(), in cui Discussione caso 2 attende fino Discussione 1 Notifica nuovo (che potrebbe mai accadere) .

miei possibili soluzioni:

a) potrei usare un CountDownLatch o di un futuro, ma entrambi hanno il problema che essi intrinsecamente eseguito solo una volta . Cioè, nel thread while di Thread 1, avrei bisogno di creare un nuovo foo per aspettare ogni volta e Thread 2 avrebbe bisogno di chiedere quale foo aspettare. Ho una brutta sensazione semplicemente scrivendo

while(true) { 
    foo = new FutureTask(); 
    ... 
    foo.set(...); 
    ...wait for a condition that might never be set... 
    ... 
} 

come temo che a foo = new FutureTask(), cosa succede quando qualcuno attese che il vecchio foo (per "qualche ragione", impostato non è stato chiamato, ad esempio, un bug nella gestione delle eccezioni)?

b) Oppure potrei usare un semaforo:

class Event { 
    Semaphore sem; 
    Event() { sem = new Semaphore(1); sem . } 
    void signal() { sem.release(); } 
    void reset() { sem.acquire(1); } 
    void wait() { if (sem.tryAcquire(1)) { sem.release(); } } 
} 

ma temo che v'è una certa condizione di competizione, se più thread sono wait() ing per esso mentre un altro segnale() s e reset ()S.

Domanda:

non c'è nulla nelle API Java analogo al comportamento degli eventi di Windows? Oppure, se si disprezza Windows, qualcosa come GolG's WaitGroup (cioè un CountDownLatch che consente countUp())? Nulla?

Come fare manualmente:

Thread 2 non può semplicemente aspettare a causa della sveglia spuria e in Java non c'è modo di sapere il motivo per cui Object.wait() ha restituito. Quindi ho bisogno di una variabile di condizione che memorizzi se l'evento è segnalato o meno. Thread 2:

synchronized(foo) { 
    while(!condition) { 
     foo.wait(); 
    } 
} 

E Thread 1 ovviamente imposta condizione su true in un blocco sincronizzato. Grazie a weekens per il suggerimento!

Esiste una classe esistente che include questo comportamento?

Oppure devo copiare e incollare il codice dappertutto?

+0

quale problema si è in realtà cercando di risolvere? Anche se sono sicuro che questo problema possa essere risolto, vorrei comunque chiederlo perché sappiamo che questa è la soluzione giusta per il tuo problema. Questo tipo di intrighi con discussioni e semafori, ben inteso, è soggetto a fallimenti a causa di piccoli errori, quindi potrebbe esserci un modo più leggibile e facile da capire per fare ciò che si vuole ottenere. In particolare suona come un semplice schema di evenlistener in cui gli ascoltatori registrati con la fonte degli eventi sarebbero più adatti. – Thor84no

+0

Sì, un listener di eventi risolverebbe il problema lasciando passare gli aggiornamenti "push" del thread 1. Questo è solitamente il metodo preferito, ad es. in un ambiente server. Tuttavia, mi sono imbattuto nel problema di thread-safe "tirando" gli aggiornamenti di volta in volta e non ho mai avuto una soluzione pulita fino ad ora. – Kosta

risposta

18

È prassi normale modificare alcuni stati durante l'esecuzione di notifyAll e controllare alcuni stati durante l'esecuzione di wait().

ad es.

Con questo approccio, non importa se il thread 1 o il thread 2 acquisiscono il blocco prima.

Alcuni strumenti di analisi di codifica ti daranno un avviso se usi la notifica o attendi senza impostare un valore o controllando un valore.

+0

Sì, è così. Grazie! – Kosta

+2

Questo non porta a una situazione di deadlock in cui Thread 2 prende 'lock' e non si sblocca finché non lascia il blocco (ma non se ne va, perché è in attesa che' ready' sia vero, cosa che non accadrà mai mentre il thread 2 contiene 'lock')? – Thor84no

+3

'lock.wait()' rilascia il 'lock' e lo riacquista prima di tornare. –

2

Il modo più semplice è solo per dire

firstThread.join();

Questo sarà il blocco fino alla fine della prima filettatura.

Ma è possibile implementare lo stesso utilizzando attesa/notifica. Sfortunatamente non hai pubblicato i tuoi frammenti di codice reale, ma suppongo che se l'attesa non esce quando chiami notifica, ciò accade perché non hai inserito entrambi nel blocco synchronized. Prestare attenzione che l'argomento "del blocco synchronized deve essere lo stesso per la coppia wait/notify.

+0

Siamo spiacenti, ma in questo caso, il primo thread viene eseguito in un ciclo while a tempo indeterminato, quindi firstThread.join() viene eseguito per sempre. wait() non esce perché viene chiamato dopo notifyAll(). I blocchi sincronizzati e wait()/notifyAll() hanno tutti gli stessi argomenti. – Kosta

3

È possibile utilizzare un wait() con timeout, nel qual caso non si rischia di attendere per sempre. Si noti inoltre che wait() può tornare anche se non vi è alcuna notifica(), quindi è necessario avvolgere l'attesa in un ciclo condizionato. Questo è il modo standard di aspettare in Java.

synchronized(syncObject) { 
    while(condition.isTrue()) { 
     syncObject.wait(WAIT_TIMEOUT); 
    } 
} 

(nel tuo thread 2)

Edit: Moved sincronizzato al di fuori del ciclo.

+0

Il problema è che Thread 2 manca il primo notifyAll(), un timeout non aiuta in questo. Ma: ho perso il risveglio spurie nella mia domanda, quindi ho bisogno di spiegare anche questo. La variabile condition è in realtà ciò di cui ho bisogno, ma preferisco while (condition.isFalse()) – Kosta

+0

Sì, è possibile utilizzare isFalse(), ovviamente. Anche il ciclo while potrebbe essere all'interno della sezione sincronizzata. Questo snippet di codice serve solo a mostrare l'idea generale. – weekens

+0

Il problema con questo è che la condizione può diventare vera dopo essere stata controllata ma prima che il blocco venga acquisito. Questo significa che finisci per aspettare() senza motivo. –

2

Vorrei usare uno BlockingQueue tra i due thread. Utilizzando wait e notify è così 5 minuti fa;)

enum Event { 
    Event, 
    Stop; 
} 

BlockingQueue<Event> queue = new LinkedBlockingQueue<Event>(); 

// Thread 1 
try { 
    while(true) { 
    ...do something... 
    queue.put(Event.Event); 
    ...wait for some condition that might never happen... 
    ... 
    } 
} finally { 
    // Tell other thread we've finished. 
    queue.put(Event.Stop}; 
} 

// Thread 2 
... 
switch (queue.take()) { 
    case Event: 
     ... 
     break; 

    default: 
     ... 
     break; 
} 
+0

Funziona, ma quello che non mi piace è che Thread 1 deve essere consapevole di quanti thread lo stanno aspettando: Se hai n Thread che sono take() ing, Thread 1 ha bisogno di mettere n eventi – Kosta

+0

@Kosta - potresti espandere questo requisito nella tua domanda per favore? Sono sicuro che possiamo fornire anche questa funzionalità. – OldCurmudgeon

+0

Penso che potresti voler controllare anche CyclicBarrier - questo gestirà tutti i lavori che hai nominato e non richiede alcun codice di livello inferiore :) - http://docs.oracle.com/javase/6/docs/api/ java/util/simultanea/CyclicBarrier.html –

Problemi correlati