Wie lässt man einen Java-Thread auf die Ausgabe eines anderen Threads warten?

128

Ich mache eine Java-Anwendung mit einem Anwendungslogik-Thread und einem Datenbankzugriffsthread. Beide bleiben während der gesamten Lebensdauer der Anwendung bestehen und müssen gleichzeitig ausgeführt werden (einer spricht mit dem Server, einer mit dem Benutzer; wenn die App vollständig gestartet ist, müssen beide funktionieren).

Beim Start muss ich jedoch sicherstellen, dass der App-Thread zunächst wartet, bis der DB-Thread bereit ist (derzeit durch Abfragen einer benutzerdefinierten Methode ermittelt dbthread.isReady()). Es würde mir nichts ausmachen, wenn der App-Thread blockiert, bis der DB-Thread fertig ist.

Thread.join() sieht nicht nach einer Lösung aus - der DB-Thread wird nur beim Herunterfahren der App beendet.

while (!dbthread.isReady()) {} Art funktioniert, aber die leere Schleife verbraucht viele Prozessorzyklen.

Irgendwelche anderen Ideen? Vielen Dank.

Piskvor verließ das Gebäude
quelle

Antworten:

128

Ich würde wirklich empfehlen, dass Sie ein Tutorial wie Suns Java Concurrency durchgehen, bevor Sie in die magische Welt des Multithreading einsteigen.

Es gibt auch eine Reihe guter Bücher (Google für "Concurrent Programming in Java", "Java Concurrency in Practice").

Um zu Ihrer Antwort zu gelangen:

In Ihrem Code, der auf das warten muss, müssen dbThreadSie Folgendes haben:

//do some work
synchronized(objectYouNeedToLockOn){
    while (!dbThread.isReady()){
        objectYouNeedToLockOn.wait();
    }
}
//continue with work after dbThread is ready

Bei Ihrer dbThreadMethode müssten Sie Folgendes tun:

//do db work
synchronized(objectYouNeedToLockOn){
    //set ready flag to true (so isReady returns true)
    ready = true;
    objectYouNeedToLockOn.notifyAll();
}
//end thread run method here

Das objectYouNeedToLockOnin diesen Beispielen verwendete Objekt ist vorzugsweise das Objekt, das Sie gleichzeitig von jedem Thread aus bearbeiten müssen, oder Sie können Objectzu diesem Zweck ein separates Objekt erstellen (ich würde nicht empfehlen, die Methoden selbst zu synchronisieren):

private final Object lock = new Object();
//now use lock in your synchronized blocks

Zum
besseren Verständnis: Es gibt andere (manchmal bessere) Möglichkeiten, dies zu tun, z. B. mit CountdownLatchesusw. Seit Java 5 gibt es im java.util.concurrentPaket und in den Unterpaketen viele raffinierte Parallelitätsklassen . Sie müssen wirklich online Material finden, um Parallelität kennenzulernen oder ein gutes Buch zu erhalten.

Herman Lintvelt
quelle
Auch kann nicht jeder Thread-Code gut in Objekte integriert werden, wenn ich mich nicht irre. Daher denke ich nicht, dass die Verwendung der Objektsynchronisation ein guter Weg ist, um diese Thread-bezogene Arbeit zu implementieren.
user1914692
@ user1914692: Sie sind sich nicht sicher, welche Fallstricke bei der Verwendung des obigen Ansatzes auftreten - möchten Sie dies näher erläutern?
Piskvor verließ das Gebäude
1
@Piskvor: Entschuldigung, ich habe das vor langer Zeit geschrieben und fast vergessen, was ich dachte. Vielleicht wollte ich nur besser die Sperre als die Objektsynchronisation verwenden, wobei letztere eine vereinfachte Form der ersteren ist.
user1914692
Ich verstehe nicht, wie das funktioniert. Wenn der Thread aauf ein Objekt wartet, synchronised(object)wie kann dann ein anderer Thread den synchronized(object)aufrufen object.notifyAll()? In meinem Programm blieb alles nur auf synchronozedBlöcken hängen.
Tomáš Zato - Wiedereinsetzung Monica
@ TomášZato Der erste Thread ruft object.wait()das Sperren dieses Objekts effektiv auf. Wenn der zweite Thread seinen synchronisierten Block "verlässt", werden die anderen Objekte aus der waitMethode freigegeben und erhalten an diesem Punkt die Sperre wieder.
Rogerdpack
140

Verwenden Sie einen CountDownLatch mit einem Zähler von 1.

CountDownLatch latch = new CountDownLatch(1);

Jetzt im App-Thread do-

latch.await();

Nachdem Sie fertig sind, tun Sie im Datenbank-Thread Folgendes:

latch.countDown();
pdeva
quelle
4
Ich mag diese Lösung wirklich, weil sie einfach ist, obwohl es auf den ersten Blick schwieriger sein könnte, die Bedeutung des Codes zu verstehen.
tödliche Gitarre
3
Für diese Verwendung müssen Sie die Verriegelung neu erstellen, wenn sie aufgebraucht ist. Um eine ähnliche Verwendung wie ein wartbares Ereignis in Windows zu erhalten, sollten Sie einen BooleanLatch oder einen zurücksetzbaren CountDownLatch versuchen: docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/… stackoverflow.com/questions / 6595835 /…
Phyatt
1
Hallo, wenn ich zuerst eine asynchrone Methode aufrufe, die ein Ereignis als solches auslösen soll: 1) asyncFunc (); 2) latch.await (); Dann zähle ich in der Ereignisbehandlungsfunktion herunter, sobald sie empfangen wurde. Wie kann ich sicherstellen, dass das Ereignis nicht behandelt wird, bevor latch.await () aufgerufen wird? Ich möchte die Vorauszahlung zwischen Zeile 1 und 2 verhindern. Vielen Dank.
NioX5199
1
Um zu vermeiden, dass Sie im Falle von Fehlern ewig warten müssen, setzen Sie countDown()einen finally{}Block ein
Daniel Alder
23

Voraussetzung ::

  1. Warten auf die Ausführung des nächsten Threads, bis der vorherige abgeschlossen ist.
  2. Der nächste Thread darf nicht gestartet werden, bis der vorherige Thread gestoppt ist, unabhängig vom Zeitaufwand.
  3. Es muss einfach und leicht zu bedienen sein.

Antworten ::

@Siehe java.util.concurrent.Future.get () doc.

future.get () Wartet bei Bedarf, bis die Berechnung abgeschlossen ist, und ruft dann das Ergebnis ab.

Job erledigt!! Siehe Beispiel unten

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

import org.junit.Test;

public class ThreadTest {

    public void print(String m) {
        System.out.println(m);
    }

    public class One implements Callable<Integer> {

        public Integer call() throws Exception {
            print("One...");
            Thread.sleep(6000);
            print("One!!");
            return 100;
        }
    }

    public class Two implements Callable<String> {

        public String call() throws Exception {
            print("Two...");
            Thread.sleep(1000);
            print("Two!!");
            return "Done";
        }
    }

    public class Three implements Callable<Boolean> {

        public Boolean call() throws Exception {
            print("Three...");
            Thread.sleep(2000);
            print("Three!!");
            return true;
        }
    }

    /**
     * @See java.util.concurrent.Future.get() doc
     *      <p>
     *      Waits if necessary for the computation to complete, and then
     *      retrieves its result.
     */
    @Test
    public void poolRun() throws InterruptedException, ExecutionException {
        int n = 3;
        // Build a fixed number of thread pool
        ExecutorService pool = Executors.newFixedThreadPool(n);
        // Wait until One finishes it's task.
        pool.submit(new One()).get();
        // Wait until Two finishes it's task.
        pool.submit(new Two()).get();
        // Wait until Three finishes it's task.
        pool.submit(new Three()).get();
        pool.shutdown();
    }
}

Ausgabe dieses Programms ::

One...
One!!
Two...
Two!!
Three...
Three!!

Sie können sehen, dass es 6 Sekunden dauert, bis die Aufgabe abgeschlossen ist, die größer als bei anderen Threads ist. Future.get () wartet also, bis die Aufgabe erledigt ist.

Wenn Sie future.get () nicht verwenden, wartet es nicht auf den Abschluss und führt den basierten Zeitverbrauch aus.

Viel Glück mit Java Parallelität.

Asche
quelle
Vielen Dank für Ihre Antwort! Ich habe CountdownLatches verwendet, aber Ihr Ansatz ist weitaus flexibler.
Piskvor verließ das Gebäude am
9

Viele richtige Antworten, aber ohne ein einfaches Beispiel. Hier ist eine einfache und einfache Verwendung CountDownLatch:

//inside your currentThread.. lets call it Thread_Main
//1
final CountDownLatch latch = new CountDownLatch(1);

//2
// launch thread#2
new Thread(new Runnable() {
    @Override
    public void run() {
        //4
        //do your logic here in thread#2

        //then release the lock
        //5
        latch.countDown();
    }
}).start();

try {
    //3 this method will block the thread of latch untill its released later from thread#2
    latch.await();
} catch (InterruptedException e) {
    e.printStackTrace();
}

//6
// You reach here after  latch.countDown() is called from thread#2
Maher Abuthraa
quelle
8
public class ThreadEvent {

    private final Object lock = new Object();

    public void signal() {
        synchronized (lock) {
            lock.notify();
        }
    }

    public void await() throws InterruptedException {
        synchronized (lock) {
            lock.wait();
        }
    }
}

Verwenden Sie diese Klasse dann wie folgt:

Erstellen Sie ein ThreadEvent:

ThreadEvent resultsReady = new ThreadEvent();

In der Methode wartet dies auf Ergebnisse:

resultsReady.await();

Und in der Methode, mit der die Ergebnisse erstellt werden, nachdem alle Ergebnisse erstellt wurden:

resultsReady.signal();

BEARBEITEN:

(Entschuldigung für die Bearbeitung dieses Beitrags, aber dieser Code hat einen sehr schlechten Rennzustand und ich habe nicht genug Ruf, um einen Kommentar abzugeben.)

Sie können dies nur verwenden, wenn Sie zu 100% sicher sind, dass signal () nach await () aufgerufen wird. Dies ist der einzige große Grund, warum Sie kein Java-Objekt wie z. B. Windows-Ereignisse verwenden können.

Wenn der Code in dieser Reihenfolge ausgeführt wird:

Thread 1: resultsReady.signal();
Thread 2: resultsReady.await();

dann wird Thread 2 für immer warten . Dies liegt daran, dass Object.notify () nur einen der aktuell ausgeführten Threads aktiviert. Ein später wartender Thread wird nicht geweckt. Dies unterscheidet sich stark von der erwarteten Funktionsweise von Ereignissen, bei denen ein Ereignis signalisiert wird, bis a) darauf gewartet oder b) explizit zurückgesetzt wird.

Hinweis: Meistens sollten Sie notifyAll () verwenden, dies ist jedoch für das oben beschriebene Problem "Für immer warten" nicht relevant.

Kosta
quelle
7

Probieren Sie die CountDownLatch- Klasse aus dem java.util.concurrentPaket aus, die Synchronisationsmechanismen auf höherer Ebene bietet, die weitaus weniger fehleranfällig sind als alle anderen Elemente auf niedriger Ebene.

WMR
quelle
6

Sie können dies mit einem Exchanger- Objekt tun, das von den beiden Threads gemeinsam genutzt wird:

private Exchanger<String> myDataExchanger = new Exchanger<String>();

// Wait for thread's output
String data;
try {
  data = myDataExchanger.exchange("");
} catch (InterruptedException e1) {
  // Handle Exceptions
}

Und im zweiten Thread:

try {
    myDataExchanger.exchange(data)
} catch (InterruptedException e) {

}

Wie andere gesagt haben, nehmen Sie diesen unbeschwerten Code nicht und kopieren Sie ihn einfach. Lesen Sie zuerst etwas.

kgiannakakis
quelle
4

Die Future- Schnittstelle aus dem java.lang.concurrentPaket bietet Zugriff auf Ergebnisse, die in einem anderen Thread berechnet wurden.

Werfen Sie einen Blick auf FutureTask und ExecutorService, um eine vorgefertigte Methode zu finden.

Ich würde jedem, der sich für Parallelität und Multithreading interessiert, dringend empfehlen, Java Concurrency In Practice zu lesen . Es konzentriert sich offensichtlich auf Java, aber es gibt viel Fleisch für jeden, der auch in anderen Sprachen arbeitet.

Bill Michell
quelle
2

Wenn Sie etwas schnelles und schmutziges wollen, können Sie einfach einen Thread.sleep () -Aufruf in Ihre while-Schleife einfügen. Wenn Sie die Datenbankbibliothek nicht ändern können, gibt es wirklich keine andere einfache Lösung. Wenn Sie die Datenbank abfragen, bis eine Wartezeit erreicht ist, wird die Leistung nicht beeinträchtigt.

while (!dbthread.isReady()) {
  Thread.sleep(250);
}

Kaum etwas, das man als eleganten Code bezeichnen könnte, aber die Arbeit erledigt.

Wenn Sie den Datenbankcode ändern können, ist es besser, einen Mutex zu verwenden, wie in anderen Antworten vorgeschlagen.

Mario Ortegón
quelle
3
Das ist so ziemlich nur damit beschäftigt zu warten. Die Verwendung von Konstrukten aus den util.concurrent-Paketen von Java 5 sollte der richtige Weg sein. stackoverflow.com/questions/289434/… scheint mir die derzeit beste Lösung zu sein.
Cem Catikkas
Es ist viel zu warten, aber wenn es nur an diesem bestimmten Ort notwendig ist und kein Zugriff auf die Datenbankbibliothek besteht, was können Sie sonst noch tun? Beschäftigtes Warten ist nicht unbedingt böse
Mario Ortegón
2

Dies gilt für alle Sprachen:

Sie möchten ein Ereignis- / Listener-Modell haben. Sie erstellen einen Listener, der auf ein bestimmtes Ereignis wartet. Das Ereignis wird in Ihrem Worker-Thread erstellt (oder signalisiert). Dadurch wird der Thread blockiert, bis das Signal empfangen wird, anstatt ständig abzufragen, ob eine Bedingung erfüllt ist, wie die derzeitige Lösung.

Ihre Situation ist eine der häufigsten Ursachen für Deadlocks. Stellen Sie sicher, dass Sie den anderen Thread unabhängig von möglicherweise aufgetretenen Fehlern signalisieren. Beispiel: Wenn Ihre Anwendung eine Ausnahme auslöst und niemals die Methode aufruft, um dem anderen zu signalisieren, dass die Dinge abgeschlossen sind. Dadurch wird der andere Thread niemals "aufwachen".

Ich schlage vor, dass Sie sich mit den Konzepten der Verwendung von Ereignissen und Ereignishandlern befassen, um dieses Paradigma besser zu verstehen, bevor Sie Ihren Fall implementieren.

Alternativ können Sie einen blockierenden Funktionsaufruf mit einem Mutex verwenden, wodurch der Thread darauf wartet, dass die Ressource frei ist. Dazu benötigen Sie eine gute Thread-Synchronisation, z. B.:

Thread-A Locks lock-a
Run thread-B
Thread-B waits for lock-a
Thread-A unlocks lock-a (causing Thread-B to continue)
Thread-A waits for lock-b 
Thread-B completes and unlocks lock-b
Klathzazt
quelle
2

Sie können in einem Thread aus einer blockierenden Warteschlange lesen und in einem anderen Thread darauf schreiben.

Ingo
quelle
1

Schon seit

  1. join() wurde ausgeschlossen
  2. Sie haben bereits CountDownLatch und verwendet
  3. Future.get () wird bereits von anderen Experten vorgeschlagen,

Sie können andere Alternativen in Betracht ziehen:

  1. invokeAll fromExecutorService

    invokeAll(Collection<? extends Callable<T>> tasks)

    Führt die angegebenen Aufgaben aus und gibt eine Liste der Futures zurück, die ihren Status und ihre Ergebnisse enthalten, wenn alle abgeschlossen sind.

  2. ForkJoinPool oder newWorkStealingPool von Executors(seit Java 8 Release)

    Erstellt einen Thread, der die Arbeit stiehlt, wobei alle verfügbaren Prozessoren als Zielparallelitätsstufe verwendet werden.

Ravindra Babu
quelle
-1

Geben Sie hier die Bildbeschreibung ein

Diese Idee kann zutreffen?. Wenn Sie CountdownLatches oder Semaphoren verwenden, funktioniert dies perfekt, aber wenn Sie nach der einfachsten Antwort für ein Interview suchen, kann dies meiner Meinung nach zutreffen.

Franco
quelle
1
Wie ist das in Ordnung für ein Interview, aber nicht für den tatsächlichen Code?
Piskvor verließ das Gebäude
Denn in diesem Fall läuft nacheinander eine Wartezeit auf eine andere. Die beste Lösung könnte die Verwendung von Semaphoren sein, da bei Verwendung von CountdownLatches, der hier die beste Antwort ist, der Thread niemals in den Ruhezustand wechselt, was die Verwendung von CPU-Zyklen bedeutet.
Franco
Aber es ging nicht darum, "sie nacheinander auszuführen". Ich werde die Frage bearbeiten, um dies klarer zu machen: Der GUI-Thread wartet, bis die Datenbank bereit ist, und dann werden beide gleichzeitig für den Rest der App-Ausführung ausgeführt: Der GUI-Thread sendet Befehle an den DB-Thread und liest die Ergebnisse. (Und noch einmal: Was ist der Sinn des Codes, der in einem Interview verwendet werden kann, aber nicht im tatsächlichen Code? Die meisten technischen Interviewer, die ich getroffen habe, hatten Hintergrundinformationen im Code und stellten dieselbe Frage. Außerdem brauchte ich dieses Ding für eine tatsächliche App Ich schrieb zu der Zeit, nicht um durch Hausaufgaben zu wieseln)
Piskvor verließ das Gebäude
1
So. Dies ist ein Hersteller-Verbraucher-Problem bei der Verwendung von Semaphoren. Ich werde versuchen, ein Beispiel zu machen
Franco
Ich habe ein Projekt erstellt github.com/francoj22/SemProducerConsumer/blob/master/src/com/… . Es funktioniert gut.
Franco