webentwicklung-frage-antwort-db.com.de

Gibt es eine "Block bis Bedingung wahr wird" -Funktion in Java?

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 habe ich Probleme mit der run-Funktion, die alle CPU-Zeitschleifen auffrisst. Die Sleep-Funktion funktioniert, aber es scheint eine Notlösung zu sein, keine Lösung.

Gibt es eine Funktion, die blockiert, bis die Variable 'condition' 'true' wird? Oder ist es die Standardmethode, in einer Endlosschleife zu warten, bis sich der Wert einer Variablen ändert?

65
Rolan

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, um Threads zu synchronisieren. Am einfachsten ist in Ihrem Fall 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 ein einfaches Object sein.

Es gibt viele andere Möglichkeiten der Kommunikation zwischen Threads. Welche verwendet werden, hängt jedoch davon ab, was Sie genau tun.

57
EboMike

EboMikes Antwort und Tobys Antwort sind beide auf dem richtigen Weg, aber beide enthalten einen fatalen Fehler. Der Fehler heißt verlorene Benachrichtigung .

Das Problem ist, dass ein Thread, der foo.notify() aufruft, überhaupt nichts tut, es sei denn, ein anderer Thread schläft bereits in einem foo.wait() -Aufruf. Das Objekt foo merkt sich nicht, dass es benachrichtigt wurde.

Es gibt einen Grund, warum Sie foo.wait() oder foo.notify() nur aufrufen dürfen, wenn der Thread auf foo synchronisiert ist. Dies liegt daran, dass die einzige Möglichkeit, einen Benachrichtigungsverlust zu vermeiden, darin besteht, die Bedingung mit einem Mutex zu schützen. Wenn es richtig gemacht ist, sieht es so aus:

Verbraucherfaden:

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

Produzenten-Thread:

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

Der Code, der die Bedingung ändert, und der Code, der die Bedingung überprüft, werden alle auf demselben 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() -Aufruf festzustecken, wenn die Bedingung bereits erfüllt ist.

Beachten Sie auch, dass sich die wait() in einer Schleife befindet. Dies ist darauf zurückzuführen, dass im Allgemeinen, wenn der Konsument die Sperre foo wiedererlangt und aufwacht, ein anderer Thread die Bedingung möglicherweise erneut falsch gemacht hat. Auch wenn dies in Ihrem Programm nicht möglich ist, ist es in einigen Betriebssystemen möglich, dass foo.wait() zurückgegeben wird, auch wenn foo.notify() wurde nicht aufgerufen. Dies wird als fälschliches Aufwecken bezeichnet. Dies ist zulässig, da sich das Warten/Benachrichtigen auf bestimmten Betriebssystemen einfacher implementieren lässt.

42
Solomon Slow

Ähnlich wie bei der Antwort von EboMike können Sie einen Mechanismus verwenden, der dem von wait/notify/notifyAll ähnelt, jedoch auf die Verwendung eines Lock ausgerichtet ist.

Beispielsweise,

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 gemeldet wird (in diesem Fall wird doSomethingElse aufgerufen), wird der erste Thread an diesem Punkt fortgesetzt ...

Die Verwendung von Locks gegenüber der intrinsischen Synchronisation hat viele Vorteile, aber ich bevorzuge es, ein explizites Condition -Objekt zu haben, um die Bedingung darzustellen (Sie können mehr als eines haben, was für Dinge wie Producer eine nette Geste ist -Verbraucher).

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 konsumieren, sondern das Interrupt-Status-Flag mit Thread.currentThrad().interrupt zurücksetzen.

Dies liegt daran, dass das Interrupt-Status-Flag zurückgesetzt wurde, wenn die Ausnahme ausgelöst wird (es heißt "Ich kann mich nicht mehr daran erinnern, unterbrochen worden zu sein, ich kann keinem anderen mitteilen, dass ich dies getan habe, wenn er danach fragt" ) und ein anderer Prozess kann sich auf diese Frage stützen. Das Beispiel ist, dass etwas anderes eine Unterbrechungsrichtlinie implementiert hat, die auf diesem ... Puh basiert. Ein weiteres Beispiel könnte sein, dass Sie eine Unterbrechungsrichtlinie haben, statt dass while(true) als while(!Thread.currentThread().isInterrupted() implementiert wurde (wodurch Ihr Code auch sozialer wird).

Zusammenfassend ist die Verwendung von Condition ungefähr gleichbedeutend mit der Verwendung von wait/notify/notifyAll, wenn Sie ein Lock verwenden möchten. Die Protokollierung ist schlecht und das Verschlucken von InterruptedException ist schlecht ungezogen;)

18
Toby

Da hat noch 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();
    }
}
16
borjab

Sie könnten ein Semaphor verwenden.

Während die Bedingung nicht erfüllt ist, erwirbt ein anderer Thread das Semaphor.
Dein Thread würde versuchen, es mit acquireUninterruptibly() zu bekommen
oder tryAcquire(int permits, long timeout, TimeUnit unit) und wäre gesperrt.

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 CountDownLatch zu verwenden.

5

Schlossfreie Lösung (?)

Ich hatte das gleiche Problem, aber ich wollte eine Lösung, die keine Sperren verwendet.

Problem: Ich habe höchstens einen Thread in einer Warteschlange. Mehrere Produzententhreads werden ständig in die Warteschlange eingefügt und müssen den Konsumenten benachrichtigen, wenn er wartet. Die Warteschlange ist nicht gesperrt, sodass die Verwendung von Sperren für Benachrichtigungen zu unnötigem Blockieren von Producer-Threads führt. Jeder Producer-Thread muss die Sperre erwerben, bevor er den wartenden Consumer benachrichtigen kann. Ich glaube, ich habe eine Lösung ohne Sperren mit LockSupport und AtomicReferenceFieldUpdater gefunden. Wenn es im JDK eine sperrfreie Barriere gibt, konnte ich sie nicht finden. Sowohl CyclicBarrier als auch CoundDownLatch verwenden intern Sperren, die ich finden konnte.

Dies ist mein leicht abgekürzter Code. Aus Gründen der Übersichtlichkeit lässt dieser Code nur einen Thread gleichzeitig warten. Es könnte modifiziert werden, um mehrere Kellner/Konsumenten zuzulassen, indem eine Art atomare Sammlung verwendet wird, um mehrere Besitzer zu speichern (ein ConcurrentMap könnte funktionieren).

Ich habe diesen Code verwendet und es scheint zu funktionieren. Ich habe es nicht ausgiebig getestet. Ich schlage vor, Sie lesen die Dokumentation für LockSupport vor der Verwendung.

/* 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 Anwendungsbeispiel zu geben, nehme ich das Beispiel von James Large an:

SignalBarrier barrier = new SignalBarrier();

Consumer Thread (Singular, nicht Plural! ):

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

Produzenten-Thread (s):

doSomethingThatMakesConditionTrue();
barrier.signal();
4
axblount