Ich schreibe einen Listener-Thread für einen Server und verwende im Moment:
while (true){
try {
if (condition){
//do something
condition=false;
}
sleep(1000);
} catch (InterruptedException ex){
Logger.getLogger(server.class.getName()).log(Level.SEVERE, null, ex);
}
}
Mit dem obigen Code stoße ich auf Probleme mit der Ausführungsfunktion, die die gesamte CPU-Zeitschleife verschlingt. Die Schlaffunktion funktioniert, aber es scheint eine provisorische Lösung zu sein, keine Lösung.
Gibt es eine Funktion, die blockieren würde, bis die Variable 'Bedingung' 'wahr' wird? Oder ist die kontinuierliche Schleife die Standardmethode zum Warten, bis sich der Wert einer Variablen ändert?
java
multithreading
block
Rolan
quelle
quelle
BlockingQueue
,Semaphore
oderCountDownLatch
eher als die Low-Level - Mechanismen.Antworten:
Eine solche Umfrage ist definitiv die am wenigsten bevorzugte Lösung.
Ich gehe davon aus, dass Sie einen anderen Thread haben, der etwas unternimmt, um die Bedingung zu erfüllen. Es gibt verschiedene Möglichkeiten, Threads zu synchronisieren. Die einfachste in Ihrem Fall wäre eine Benachrichtigung über ein Objekt:
Haupt-Bedroung:
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. } }
Anderer Thread:
// Do something // If the condition is true, do the following: synchronized(syncObject) { syncObject.notify(); }
syncObject
selbst kann eine einfache seinObject
.Es gibt viele andere Möglichkeiten der Kommunikation zwischen Threads, aber welche verwendet werden soll, hängt davon ab, was genau Sie tun.
quelle
notfify
anstelle vonnotifyAll
zu lustigen Effekten führen kann, wenn ein anderer Thread ebenfalls auf diese Weise wartet, danotify
nur einer der wartenden Threads benachrichtigt wird (vorausgesetzt, dass dies der Fall ist) zufällig).Die Antwort von EboMike und Toby sind beide auf dem richtigen Weg, aber beide enthalten einen fatalen Fehler. Der Fehler wird als verlorene Benachrichtigung bezeichnet .
Das Problem ist, wenn ein Thread aufruft
foo.notify()
, wird er überhaupt nichts tun, es sei denn, ein anderer Thread schläft bereits in einemfoo.wait()
Aufruf. Das Objektfoo
erinnert sich nicht daran, dass es benachrichtigt wurde.Es gibt einen Grund, warum Sie nicht anrufen dürfen
foo.wait()
oder esfoo.notify()
sei denn, der Thread ist auf foo synchronisiert. Dies liegt daran, dass der einzige Weg, um eine verlorene Benachrichtigung zu vermeiden, darin besteht, den Zustand mit einem Mutex zu schützen. Wenn es richtig gemacht ist, sieht es so aus:Verbraucher-Thread:
try { synchronized(foo) { while(! conditionIsTrue()) { foo.wait(); } doSomethingThatRequiresConditionToBeTrue(); } } catch (InterruptedException e) { handleInterruption(); }
Produzententhread:
synchronized(foo) { doSomethingThatMakesConditionTrue(); foo.notify(); }
Der Code, der die Bedingung ändert, und der Code, der die Bedingung überprüft, werden alle für dasselbe Objekt synchronisiert, und der Consumer-Thread testet die Bedingung explizit, bevor er wartet. Es gibt keine Möglichkeit für den Verbraucher, die Benachrichtigung zu verpassen und für immer in einem
wait()
Anruf zu stecken , wenn die Bedingung bereits erfüllt ist.Beachten Sie auch, dass sich das
wait()
in einer Schleife befindet. Dies liegt daran, dass im Allgemeinen, wenn der Verbraucher diefoo
Sperre wiedererlangt und aufwacht, ein anderer Thread die Bedingung möglicherweise erneut falsch gemacht hat. Auch wenn dies in Ihrem Programm nicht möglich ist, können Sie in einigen Betriebssystemenfoo.wait()
auch dann zurückkehren, wenn diesfoo.notify()
nicht aufgerufen wurde. Dies wird als falsches Aufwecken bezeichnet und ist zulässig, da es die Implementierung von Wartezeiten / Benachrichtigungen auf bestimmten Betriebssystemen erleichtert.quelle
InterruptedException
, richtig? Es liegt an Ihnen, zu entscheiden, was ein Interrupt bedeutet, aber in den meisten Fällen bedeutet dies wahrscheinlich "aufhören zu warten" und etwas anderes zu tun (wie zum Beispiel das gesamte Programm herunterzufahren). In den meisten Fällen möchten Sie es also um wie in meinem obigen Beispiel auszusehen, mit dem Interrupt-Handler außerhalb der Schleife.foo.wait()
wird werfen,IllegalMonitorStateException
wennfoo
nicht gesperrt ist. Das soll Sie daran erinnern, dass eswait()
in Code, der die Sperre nicht hält, keinen Sinn macht. Meine Antwort oben berührt den Grund dafür, aber wenn Sie eine gründliche Erklärung wünschen, sollten Sie das Tutorial lesen. docs.oracle.com/javase/tutorial/essential/concurrency/…synchronized(foo)
, sie wird jedoch blockiert, da der Produzent bereits synchronisiertfoo
ist. (3) Der Produzent bewirkt, dass die Bedingung wahr wird, ruft auffoo.notify()
und Dann wird die Sperre aufgehoben. (4) Der Verbraucher betritt densynchronized(foo)
Block und ruft anfoo.wait()
. Jetzt wartet der Verbraucher nicht mehr auf eine Benachrichtigung, die niemals eintrifft. Dieses Problem wird manchmal als "Benachrichtigung verloren" bezeichnet.Da hat niemand eine Lösung mit CountDownLatch veröffentlicht. Wie wäre es mit:
public class Lockeable { private final CountDownLatch countDownLatch = new CountDownLatch(1); public void doAfterEvent(){ countDownLatch.await(); doSomething(); } public void reportDetonatingEvent(){ countDownLatch.countDown(); } }
quelle
Ähnlich wie bei der Antwort von EboMike können Sie einen ähnlichen Mechanismus wie wait / notify / notifyAll verwenden, der jedoch auf die Verwendung von a ausgerichtet ist
Lock
.Zum Beispiel,
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(); } }
Wenn Sie auf eine Bedingung warten, die von einem anderen Thread benachrichtigt wird (in diesem Fall wird aufgerufen
doSomethingElse
), wird an diesem Punkt der erste Thread fortgesetzt ...Die Verwendung von
Lock
s gegenüber der intrinsischen Synchronisation hat viele Vorteile, aber ich bevorzuge nur ein explizitesCondition
Objekt, um die Bedingung darzustellen (Sie können mehr als eines haben, was für Dinge wie Produzent-Konsument eine nette Geste ist).Ich kann auch nicht anders, als zu bemerken, wie Sie mit der unterbrochenen Ausnahme in Ihrem Beispiel umgehen. Sie sollten die Ausnahme wahrscheinlich nicht wie folgt verwenden, sondern stattdessen das Interrupt-Status-Flag mit zurücksetzen
Thread.currentThread().interrupt
.Dies liegt daran, dass, wenn die Ausnahme ausgelöst wird, das Interrupt-Status-Flag zurückgesetzt wurde (es heißt " Ich erinnere mich nicht mehr daran, unterbrochen worden zu sein, ich kann niemand anderem sagen, dass ich es war, wenn er danach fragt ") und ein anderer Prozess möglicherweise Verlassen Sie sich auf diese Frage. Das Beispiel ist, dass etwas anderes eine Unterbrechungsrichtlinie implementiert hat, die auf diesem ... Puh basiert. Ein weiteres Beispiel könnte sein, dass Ihre Unterbrechungsrichtlinie eher
while(true)
implementiert wurde alswhile(!Thread.currentThread().isInterrupted()
(was auch dazu führt, dass Ihr Code ... sozial rücksichtsvoller wird).Zusammenfassend ist die Verwendung
Condition
also ungefähr gleichbedeutend mit der Verwendung von wait / notify / notifyAll, wenn Sie a verwenden möchtenLock
. Die Protokollierung ist böse und das SchluckenInterruptedException
ist ungezogen;)quelle
Condition
+Lock
ist nicht äquivalent zu denObject
Synchronisationsmethoden +synchronized
. Ersteres erlaubt Benachrichtigungen, bevor die Bedingung erwartet wird. Wenn Sie jedochObject.notify()
zuvor aufrufenObject.wait()
, wird der Thread für immer blockiert. Außerdemawait()
muss in einer Schleife aufgerufen werden, siehe docs.Condition
Text gesucht, der diese Behauptung stützt , konnte ihn jedoch nicht finden. Können Sie bitte Ihre Aussage erklären?Sie könnten ein Semaphor verwenden .
Während die Bedingung nicht erfüllt ist, erfasst ein anderer Thread das Semaphor.
Ihr Thread würde versuchen, es mit
acquireUninterruptibly()
oder zu erwerben
tryAcquire(int permits, long timeout, TimeUnit unit)
und würde blockiert werden.Wenn die Bedingung erfüllt ist, wird auch das Semaphor freigegeben und Ihr Thread würde es erwerben.
Sie können auch versuchen, ein
SynchronousQueue
oder ein zu verwendenCountDownLatch
.quelle
Schlossfreie Lösung (?)
Ich hatte das gleiche Problem, wollte aber eine Lösung, bei der keine Sperren verwendet wurden.
Problem: Ich habe höchstens einen Thread aus einer Warteschlange. Mehrere Producer-Threads werden ständig in die Warteschlange eingefügt und müssen den Consumer benachrichtigen, wenn er wartet. Die Warteschlange ist sperrenfrei, sodass die Verwendung von Sperren für Benachrichtigungen zu unnötigen Blockierungen in Producer-Threads führt. Jeder Producer-Thread muss die Sperre erwerben, bevor er den wartenden Consumer benachrichtigen kann. Ich glaube , ich kam mit einer Lock-freier Lösung mit
LockSupport
undAtomicReferenceFieldUpdater
. Wenn im JDK eine sperrfreie Barriere vorhanden ist, konnte ich sie nicht finden. BeideCyclicBarrier
undCoundDownLatch
verwenden intern Sperren von dem, was ich finden konnte.Dies ist mein leicht abgekürzter Code. Mit diesem Code kann nur ein Thread gleichzeitig warten. Es könnte geändert werden, um mehrere Kellner / Verbraucher zuzulassen, indem eine Art atomare Sammlung zum Speichern mehrerer Eigentümer verwendet wird (a
ConcurrentMap
funktioniert möglicherweise).Ich habe diesen Code verwendet und es scheint zu funktionieren. Ich habe es nicht ausgiebig getestet. Ich empfehle Ihnen, die Dokumentation
LockSupport
vor der Verwendung zu lesen ./* 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<SignalBarrier, Thread> 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); } }
Um ein vages Beispiel für die Verwendung zu geben, werde ich das Beispiel von James Large übernehmen:
SignalBarrier barrier = new SignalBarrier();
Verbraucher-Thread (Singular, nicht Plural! ):
try { while(!conditionIsTrue()) { barrier.await(); } doSomethingThatRequiresConditionToBeTrue(); } catch (InterruptedException e) { handleInterruption(); }
Produzenten-Thread (s):
quelle