c’è una funzione “blocca finché la condizione diventa vera” in java?

Sto scrivendo un thread listener per un server e al momento sto usando:

while (true){ try { if (condition){ //do something condition=false; } sleep(1000); } catch (InterruptedException ex){ Logger.getLogger(server.class.getName()).log(Level.SEVERE, null, ex); } } 

Con il codice sopra, sto incontrando problemi con la funzione di esecuzione che consuma tutto il ciclo di tempo della CPU. La funzione sleep funziona, ma sembra una soluzione improvvisata, non una soluzione.

Esiste qualche funzione che bloccherebbe fino a quando la ‘condizione’ della variabile diventa ‘vera’? O è il ciclo continuo del metodo standard di attesa fino a quando il valore di una variabile non cambia?

Polling come questo è sicuramente la soluzione meno preferita.

Presumo che tu abbia un altro thread che farà qualcosa per rendere vera la condizione. Esistono diversi modi per sincronizzare i thread. Il più semplice nel tuo caso sarebbe una notifica tramite un object:

Filo principale:

 synchronized(syncObject) { try { // Calling wait() will block this thread until another thread // calls notify() on the object. syncObject.wait(); } catch (InterruptedException e) { // Happens if someone interrupts your thread. } } 

Altro thread:

 // Do something // If the condition is true, do the following: synchronized(syncObject) { syncObject.notify(); } 

syncObject stesso può essere un semplice Object .

Esistono molti altri modi di comunicazione tra thread, ma quale da usare dipende da cosa stai facendo esattamente.

La risposta di EboMike e la risposta di Toby sono entrambe sulla buona strada, ma entrambi contengono un difetto fatale. Il difetto è chiamato notifica persa .

Il problema è che se un thread chiama foo.notify() , non farà nulla a meno che qualche altro thread stia già dormendo in una chiamata foo.wait() . L’object, foo , non ricorda che è stato notificato.

C’è un motivo per cui non ti è permesso chiamare foo.wait() o foo.notify() meno che il thread non sia sincronizzato su foo. Perché l’unico modo per evitare notifiche perse è proteggere la condizione con un mutex. Quando è fatto bene, sembra che questo:

Filo del consumatore:

 try { synchronized(foo) { while(! conditionIsTrue()) { foo.wait(); } doSomethingThatRequiresConditionToBeTrue(); } } catch (InterruptedException e) { handleInterruption(); } 

Thread del produttore:

 synchronized(foo) { doSomethingThatMakesConditionTrue(); foo.notify(); } 

Il codice che modifica la condizione e il codice che controlla la condizione è tutto sincronizzato sullo stesso object e il thread consumatore verifica in modo esplicito la condizione prima che sia in attesa. Non è ansible per il consumatore perdere la notifica e rimanere bloccato per sempre in una chiamata wait() quando la condizione è già vera.

Si noti inoltre che l’ wait() è in un ciclo. Questo perché, nel caso generale, quando il consumatore riacquista il blocco foo e si ritriggers, qualche altro thread potrebbe aver reso nuovamente false le condizioni. Anche se ciò non è ansible nel tuo programma, ciò che è ansible, in alcuni sistemi operativi, è che foo.wait() ritorni anche quando non è stato chiamato foo.notify() . Si chiama wakeful spurie e può accadere perché rende l’attesa / notifica più facile da implementare su determinati sistemi operativi.

Simile alla risposta di EboMike, è ansible utilizzare un meccanismo simile a wait / notify / notifyAll ma predisposto per l’utilizzo di un Lock .

Per esempio,

 public void doSomething() throws InterruptedException { lock.lock(); try { condition.await(); // releases lock and waits until doSomethingElse is called } finally { lock.unlock(); } } public void doSomethingElse() { lock.lock(); try { condition.signal(); } finally { lock.unlock(); } } 

Dove attenderete qualche condizione che viene notificata da un altro thread (in questo caso chiama doSomethingElse ), a quel punto, il primo thread continuerà …

L’utilizzo della sincronizzazione intrinseca di Lock s ha molti vantaggi ma preferisco avere un object Condition esplicito per rappresentare la condizione (puoi avere più di uno che è un bel touch per cose come produttore-consumatore).

Inoltre, non posso fare a meno di notare come gestisci l’eccezione interrotta nell’esempio. Probabilmente non dovresti consumare l’eccezione come questa, resetta invece il flag di stato dell’interrupt usando Thread.currentThrad().interrupt .

Questo perché se viene lanciata l’eccezione, il flag di stato dell’interrupt sarà stato resettato (sta dicendo ” Non ricordo più di essere stato interrotto, non sarò in grado di dire a nessuno che sono stato se lo chiedono “) e un altro processo potrebbe fare affidamento su questa domanda. L’esempio è che qualcos’altro ha implementato un criterio di interruzione basato su questo … phew. Un ulteriore esempio potrebbe essere che tu sei un criterio di interruzione, piuttosto che while(true) potrebbe essere stato implementato while(!Thread.currentThread().isInterrupted() (che renderà anche il tuo codice più … socialmente rispettoso) .

Quindi, in sintesi, l’utilizzo di Condition è rougly equivalente all’utilizzo di wait / notify / notifyAll quando si desidera utilizzare un Lock , la registrazione è malvagia e la deglutizione InterruptedException è ctriggers;)

Come nessuno ha pubblicato una soluzione con CountDownLatch. Che dire:

 public class Lockeable { private final CountDownLatch countDownLatch = new CountDownLatch(1); public void doAfterEvent(){ countDownLatch.await(); doSomething(); } public void reportDetonatingEvent(){ countDownLatch.countDown(); } } 

Potresti usare un semaforo .

Mentre la condizione non è soddisfatta, un altro thread acquisisce il semaforo.
Il tuo thread cercherebbe di acquisirlo con acquireUninterruptibly()
oppure tryAcquire(int permits, long timeout, TimeUnit unit) e verrebbe bloccato.

Quando la condizione è soddisfatta, anche il semaforo viene rilasciato e il thread lo acquisisce.

Si potrebbe anche provare a utilizzare un SynchronousQueue o un CountDownLatch .

Soluzione senza blocco (?)

Ho avuto lo stesso problema, ma volevo una soluzione che non usasse i lucchetti.

Problema: ho al massimo un thread che consuma da una coda. I thread di più produttori vengono costantemente inseriti nella coda e devono essere notificati al consumatore se è in attesa. La coda è priva di blocchi, quindi l’utilizzo di blocchi per la notifica provoca blocchi non necessari nei thread del produttore. Ogni thread del produttore deve acquisire il blocco prima che possa informare il consumatore in attesa. Credo di aver trovato una soluzione lock-free usando LockSupport e AtomicReferenceFieldUpdater . Se all’interno del JDK esiste una barriera senza blocco, non potrei trovarla. Sia CyclicBarrier che CoundDownLatch utilizzano i blocchi internamente da ciò che ho trovato.

Questo è il mio codice un po ‘abbreviato. Per essere chiari, questo codice consentirà solo un thread in attesa alla volta. Potrebbe essere modificato per consentire più utenti / consumatori utilizzando un tipo di raccolta atomica per archiviare più proprietari (può funzionare ConcurrentMap ).

Ho usato questo codice e sembra funzionare. Non l’ho testato estesamente. Suggerisco di leggere la documentazione di LockSupport prima dell’uso.

 /* I release this code into the public domain. * http://unlicense.org/UNLICENSE */ import java.util.concurrent.atomic.AtomicReferenceFieldUpdater; import java.util.concurrent.locks.LockSupport; /** * A simple barrier for awaiting a signal. * Only one thread at a time may await the signal. */ public class SignalBarrier { /** * The Thread that is currently awaiting the signal. * !!! Don't call this directly !!! */ @SuppressWarnings("unused") private volatile Thread _owner; /** Used to update the owner atomically */ private static final AtomicReferenceFieldUpdater ownerAccess = AtomicReferenceFieldUpdater.newUpdater(SignalBarrier.class, Thread.class, "_owner"); /** Create a new SignalBarrier without an owner. */ public SignalBarrier() { _owner = null; } /** * Signal the owner that the barrier is ready. * This has no effect if the SignalBarrer is unowned. */ public void signal() { // Remove the current owner of this barrier. Thread t = ownerAccess.getAndSet(this, null); // If the owner wasn't null, unpark it. if (t != null) { LockSupport.unpark(t); } } /** * Claim the SignalBarrier and block until signaled. * * @throws IllegalStateException If the SignalBarrier already has an owner. * @throws InterruptedException If the thread is interrupted while waiting. */ public void await() throws InterruptedException { // Get the thread that would like to await the signal. Thread t = Thread.currentThread(); // If a thread is attempting to await, the current owner should be null. if (!ownerAccess.compareAndSet(this, null, t)) { throw new IllegalStateException("A second thread tried to acquire a signal barrier that is already owned."); } // The current thread has taken ownership of this barrier. // Park the current thread until the signal. Record this // signal barrier as the 'blocker'. LockSupport.park(this); // If a thread has called #signal() the owner should already be null. // However the documentation for LockSupport.unpark makes it clear that // threads can wake up for absolutely no reason. Do a compare and set // to make sure we don't wipe out a new owner, keeping in mind that only // thread should be awaiting at any given moment! ownerAccess.compareAndSet(this, t, null); // Check to see if we've been unparked because of a thread interrupt. if (t.isInterrupted()) throw new InterruptedException(); } /** * Claim the SignalBarrier and block until signaled or the timeout expires. * * @throws IllegalStateException If the SignalBarrier already has an owner. * @throws InterruptedException If the thread is interrupted while waiting. * * @param timeout The timeout duration in nanoseconds. * @return The timeout minus the number of nanoseconds that passed while waiting. */ public long awaitNanos(long timeout) throws InterruptedException { if (timeout < = 0) return 0; // Get the thread that would like to await the signal. Thread t = Thread.currentThread(); // If a thread is attempting to await, the current owner should be null. if (!ownerAccess.compareAndSet(this, null, t)) { throw new IllegalStateException("A second thread tried to acquire a signal barrier is already owned."); } // The current thread owns this barrier. // Park the current thread until the signal. Record this // signal barrier as the 'blocker'. // Time the park. long start = System.nanoTime(); LockSupport.parkNanos(this, timeout); ownerAccess.compareAndSet(this, t, null); long stop = System.nanoTime(); // Check to see if we've been unparked because of a thread interrupt. if (t.isInterrupted()) throw new InterruptedException(); // Return the number of nanoseconds left in the timeout after what we // just waited. return Math.max(timeout - stop + start, 0L); } } 

Per dare un vago esempio di utilizzo, adotterò l'esempio di james large:

 SignalBarrier barrier = new SignalBarrier(); 

Filo dei consumatori (singolare, non plurale! ):

 try { while(!conditionIsTrue()) { barrier.await(); } doSomethingThatRequiresConditionToBeTrue(); } catch (InterruptedException e) { handleInterruption(); } 

Thread (i) di prodotto:

 doSomethingThatMakesConditionTrue(); barrier.signal();