Woher wissen, ob andere Threads beendet sind?

125

Ich habe ein Objekt mit einer Methode namens StartDownload(), die drei Threads startet.

Wie erhalte ich eine Benachrichtigung, wenn die Ausführung jedes Threads abgeschlossen ist?

Gibt es eine Möglichkeit festzustellen, ob einer (oder alle) Threads fertig sind oder noch ausgeführt werden?

Ricardo Felgueiras
quelle
1
Werfen

Antworten:

227

Es gibt verschiedene Möglichkeiten, dies zu tun:

  1. Verwenden Sie Thread.join () in Ihrem Hauptthread, um blockierend auf den Abschluss jedes Threads zu warten, oder
  2. Überprüfen Sie Thread.isAlive () in einer Abfragemethode - im Allgemeinen nicht empfohlen -, um zu warten, bis jeder Thread abgeschlossen ist, oder
  3. Unorthodox, rufen Sie für jeden fraglichen Thread setUncaughtExceptionHandler auf, um eine Methode in Ihrem Objekt aufzurufen, und programmieren Sie jeden Thread so, dass er nach Abschluss eine nicht erfasste Ausnahme auslöst , oder
  4. Verwenden Sie Sperren oder Synchronisierer oder Mechanismen aus java.util.concurrent oder
  5. Orthodoxer, erstellen Sie einen Listener in Ihrem Haupt-Thread und programmieren Sie dann jeden Ihrer Threads, um dem Listener mitzuteilen, dass sie abgeschlossen sind.

Wie implementiere ich Idee 5? Eine Möglichkeit besteht darin, zuerst eine Schnittstelle zu erstellen:

public interface ThreadCompleteListener {
    void notifyOfThreadComplete(final Thread thread);
}

Erstellen Sie dann die folgende Klasse:

public abstract class NotifyingThread extends Thread {
  private final Set<ThreadCompleteListener> listeners
                   = new CopyOnWriteArraySet<ThreadCompleteListener>();
  public final void addListener(final ThreadCompleteListener listener) {
    listeners.add(listener);
  }
  public final void removeListener(final ThreadCompleteListener listener) {
    listeners.remove(listener);
  }
  private final void notifyListeners() {
    for (ThreadCompleteListener listener : listeners) {
      listener.notifyOfThreadComplete(this);
    }
  }
  @Override
  public final void run() {
    try {
      doRun();
    } finally {
      notifyListeners();
    }
  }
  public abstract void doRun();
}

und dann wird jeder Ihrer Threads erweitert NotifyingThreadund anstatt ihn zu implementieren run(), wird er implementiert doRun(). Wenn sie abgeschlossen sind, benachrichtigen sie automatisch jeden, der auf eine Benachrichtigung wartet.

Schließlich ändern Sie in Ihrer Hauptklasse - der Klasse, die alle Threads startet (oder zumindest das Objekt, das auf eine Benachrichtigung wartet) - diese Klasse implement ThreadCompleteListenerund fügen sich unmittelbar nach dem Erstellen jedes Threads der Liste der Listener hinzu:

NotifyingThread thread1 = new OneOfYourThreads();
thread1.addListener(this); // add ourselves as a listener
thread1.start();           // Start the Thread

Wenn jeder Thread beendet wird, wird Ihre notifyOfThreadCompleteMethode mit der gerade abgeschlossenen (oder abgestürzten) Thread-Instanz aufgerufen.

Beachten Sie, dass es besser wäre, implements Runnableals extends Threadfür NotifyingThreaddas Erweitern von Threads in neuem Code normalerweise nicht zu empfehlen. Aber ich codiere auf Ihre Frage. Wenn Sie die NotifyingThreadzu implementierende Klasse ändern Runnable, müssen Sie einen Teil Ihres Codes ändern, der Threads verwaltet, was ziemlich einfach ist.

Eddie
quelle
Hallo!! Ich mag die letzte Idee. Ich soll einen Listener implementieren, um das zu tun? Vielen Dank
Ricardo Felgueiras
4
Bei diesem Ansatz werden die notifiyListeners jedoch innerhalb von run () aufgerufen, sodass sie innerhalb des Threads aufgerufen werden und die weiteren Aufrufe auch dort ausgeführt werden. Ist das nicht so?
Jordi Puigdellívol
1
@Eddie Jordi fragte, ob es möglich sei, die notifyMethode nicht innerhalb der runMethode, sondern danach aufzurufen .
Tomasz Dzięcielewski
1
Die Frage ist wirklich: Wie bekommt man OFF jetzt des sekundären Thread. Ich weiß, dass es fertig ist, aber wie greife ich jetzt auf den Haupt- Thread zu?
Patrick
1
Ist dieser Thread sicher? Es scheint, dass notifyListeners (und damit notifyOfThreadComplete) innerhalb des NotifyingThread und nicht innerhalb des Threads aufgerufen werden, der den Listener selbst erstellt hat.
Aaron
13

Lösung mit CyclicBarrier

public class Downloader {
  private CyclicBarrier barrier;
  private final static int NUMBER_OF_DOWNLOADING_THREADS;

  private DownloadingThread extends Thread {
    private final String url;
    public DownloadingThread(String url) {
      super();
      this.url = url;
    }
    @Override
    public void run() {
      barrier.await(); // label1
      download(url);
      barrier.await(); // label2
    }
  }
  public void startDownload() {
    // plus one for the main thread of execution
    barrier = new CyclicBarrier(NUMBER_OF_DOWNLOADING_THREADS + 1); // label0
    for (int i = 0; i < NUMBER_OF_DOWNLOADING_THREADS; i++) {
      new DownloadingThread("http://www.flickr.com/someUser/pic" + i + ".jpg").start();
    }
    barrier.await(); // label3
    displayMessage("Please wait...");
    barrier.await(); // label4
    displayMessage("Finished");
  }
}

label0 - Die zyklische Barriere wird mit einer Anzahl von Parteien erstellt, die der Anzahl der ausgeführten Threads plus einer für den Hauptausführungsthread entspricht (in dem startDownload () ausgeführt wird).

Etikett 1 - n-ter DownloadingThread betritt den Warteraum

Label 3 - NUMBER_OF_DOWNLOADING_THREADS haben den Warteraum betreten. Der Hauptthread der Ausführung gibt sie frei, um ihre Download-Jobs mehr oder weniger zur gleichen Zeit auszuführen

Etikett 4 - Hauptthread der Ausführung betritt den Warteraum. Dies ist der "schwierigste" Teil des Codes, den es zu verstehen gilt. Es spielt keine Rolle, welcher Thread zum zweiten Mal in den Warteraum gelangt. Es ist wichtig, dass jeder Thread, der zuletzt den Raum betritt, sicherstellt, dass alle anderen Download-Threads ihre Download-Jobs beendet haben.

label 2 - n-th DownloadingThread hat den Download-Auftrag beendet und betritt den Warteraum. Wenn es der letzte ist, dh bereits NUMBER_OF_DOWNLOADING_THREADS eingegeben hat, einschließlich des Hauptthreads der Ausführung, setzt der Hauptthread seine Ausführung erst fort, wenn alle anderen Threads den Download abgeschlossen haben.

Boris Pavlović
quelle
9

Sie sollten wirklich eine Lösung bevorzugen, die verwendet java.util.concurrent. Finden und lesen Sie Josh Bloch und / oder Brian Goetz zum Thema.

Wenn Sie java.util.concurrent.*Threads nicht direkt verwenden und die Verantwortung dafür übernehmen, sollten Sie wahrscheinlich verwenden, um join()zu wissen, wann ein Thread fertig ist. Hier ist ein super einfacher Rückrufmechanismus. Erweitern Sie zuerst die RunnableSchnittstelle, um einen Rückruf zu erhalten:

public interface CallbackRunnable extends Runnable {
    public void callback();
}

Erstellen Sie dann einen Executor, der Ihre ausführbare Datei ausführt, und rufen Sie zurück, wenn dies erledigt ist.

public class CallbackExecutor implements Executor {

    @Override
    public void execute(final Runnable r) {
        final Thread runner = new Thread(r);
        runner.start();
        if ( r instanceof CallbackRunnable ) {
            // create a thread to perform the callback
            Thread callerbacker = new Thread(new Runnable() {
                @Override
                public void run() {
                    try {
                        // block until the running thread is done
                        runner.join();
                        ((CallbackRunnable)r).callback();
                    }
                    catch ( InterruptedException e ) {
                        // someone doesn't want us running. ok, maybe we give up.
                    }
                }
            });
            callerbacker.start();
        }
    }

}

Die andere offensichtliche Sache, die Sie Ihrer CallbackRunnableSchnittstelle hinzufügen müssen, ist ein Mittel, um Ausnahmen zu behandeln. public void uncaughtException(Throwable e);Fügen Sie also möglicherweise eine Zeile ein und installieren Sie in Ihrem Executor einen Thread.UncaughtExceptionHandler, um Sie an diese Schnittstellenmethode zu senden.

Aber all das zu tun, fängt wirklich an zu riechen java.util.concurrent.Callable. Sie sollten sich wirklich die Verwendung ansehen, java.util.concurrentwenn Ihr Projekt dies zulässt.

broc.seib
quelle
Ich bin mir ein wenig unklar, was Sie von diesem Rückrufmechanismus profitieren, anstatt einfach aufzurufen runner.join()und dann den gewünschten Code danach, da Sie wissen, dass der Thread beendet ist. Ist es nur so, dass Sie diesen Code als eine Eigenschaft der ausführbaren Datei definieren können, sodass Sie für verschiedene ausführbare Dateien unterschiedliche Funktionen haben können?
Stephen
2
Ja, runner.join()ist der direkteste Weg zu warten. Ich ging davon aus, dass das OP seinen Hauptaufrufthread nicht blockieren wollte, da sie darum baten, für jeden Download "benachrichtigt" zu werden, der in beliebiger Reihenfolge abgeschlossen werden konnte. Dies bot eine Möglichkeit, asynchron benachrichtigt zu werden.
Broc.seib
4

Möchten Sie warten, bis sie fertig sind? Verwenden Sie in diesem Fall die Join-Methode.

Es gibt auch die Eigenschaft isAlive, wenn Sie sie nur überprüfen möchten.

Jonathan Allen
quelle
3
Beachten Sie, dass isAlive false zurückgibt, wenn der Thread noch nicht ausgeführt wurde (auch wenn Ihr eigener Thread bereits start on aufgerufen hat).
Tom Hawtin - Tackline
@ TomHawtin-Tackline bist du dir da ganz sicher? Dies würde der Java-Dokumentation widersprechen ("Ein Thread lebt, wenn er gestartet wurde und noch nicht gestorben ist" - docs.oracle.com/javase/6/docs/api/java/lang/… ). Es würde auch den Antworten hier widersprechen ( stackoverflow.com/questions/17293304/… )
Stephen
@ Stephen Es ist lange her, dass ich das geschrieben habe, aber es scheint wahr zu sein. Ich stelle mir vor, es hat anderen Menschen Probleme bereitet, die mir vor neun Jahren noch in Erinnerung geblieben sind. Was genau beobachtbar ist, hängt von der Implementierung ab. Sie sagen a Threadto start, was der Thread tut, aber der Aufruf kehrt sofort zurück. isAlivesollte ein einfacher Flaggentest sein, aber als ich ihn googelte, war die Methode native.
Tom Hawtin - Tackline
4

Sie können die Thread-Instanz mit getState () abfragen, das eine Instanz der Thread.State-Enumeration mit einem der folgenden Werte zurückgibt:

*  NEW
  A thread that has not yet started is in this state.
* RUNNABLE
  A thread executing in the Java virtual machine is in this state.
* BLOCKED
  A thread that is blocked waiting for a monitor lock is in this state.
* WAITING
  A thread that is waiting indefinitely for another thread to perform a particular action is in this state.
* TIMED_WAITING
  A thread that is waiting for another thread to perform an action for up to a specified waiting time is in this state.
* TERMINATED
  A thread that has exited is in this state.

Ich denke jedoch, es wäre ein besseres Design, einen Master-Thread zu haben, der darauf wartet, dass die 3 Kinder fertig sind. Der Master würde dann die Ausführung fortsetzen, wenn die anderen 3 fertig sind.

Miquel
quelle
Das Warten auf das Verlassen der drei Kinder passt möglicherweise nicht in das Nutzungsparadigma. Wenn es sich um einen Download-Manager handelt, möchten sie möglicherweise 15 Downloads starten und einfach den Status aus der Statusleiste entfernen oder den Benutzer benachrichtigen, wenn ein Download abgeschlossen ist. In diesem Fall würde ein Rückruf besser funktionieren.
Digitaljoel
3

Sie können das ExecutorsObjekt auch verwenden , um einen ExecutorService- Thread-Pool zu erstellen . Verwenden Sie dann die invokeAllMethode, um jeden Ihrer Threads auszuführen und Futures abzurufen. Dies wird blockiert, bis alle die Ausführung beendet haben. Ihre andere Möglichkeit wäre, jeden einzelnen mithilfe des Pools auszuführen und dann awaitTerminationzum Blockieren aufzurufen , bis die Ausführung des Pools abgeschlossen ist. Rufen shutdownSie einfach () auf, wenn Sie mit dem Hinzufügen von Aufgaben fertig sind.

J Gitter
quelle
2

Viele Dinge wurden in den letzten 6 Jahren an der Multithreading-Front geändert.

Anstatt die join()API zu verwenden und zu sperren, können Sie sie verwenden

1. ExecutorService invokeAll() API

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

Eine Synchronisationshilfe, mit der ein oder mehrere Threads warten können, bis eine Reihe von Vorgängen in anderen Threads abgeschlossen ist.

A CountDownLatchwird mit einer bestimmten Anzahl initialisiert. Die Wartemethoden blockieren, bis der aktuelle Zählerstand aufgrund von Aufrufen der countDown()Methode Null erreicht. Danach werden alle wartenden Threads freigegeben und alle nachfolgenden Aufrufe von Wartezeiten werden sofort zurückgegeben. Dies ist ein One-Shot-Phänomen - die Zählung kann nicht zurückgesetzt werden. Wenn Sie eine Version benötigen, die die Anzahl zurücksetzt, sollten Sie einen CyclicBarrier verwenden.

3. ForkJoinPool oder newWorkStealingPool()in Executors ist eine andere Möglichkeit

4. Durchlaufen Sie alle FutureAufgaben ab dem Senden ExecutorServiceund überprüfen Sie den Status, indem Sie den Aufruf get()des FutureObjekts blockieren

Schauen Sie sich verwandte SE-Fragen an:

Wie kann man auf einen Thread warten, der seinen eigenen Thread erzeugt?

Ausführende: Wie kann ich synchron warten, bis alle Aufgaben abgeschlossen sind, wenn Aufgaben rekursiv erstellt werden?

Ravindra Babu
quelle
2

Ich würde vorschlagen, sich den Javadoc für die Thread- Klasse anzusehen .

Sie haben mehrere Mechanismen zur Thread-Manipulation.

  • Ihr Haupt-Thread könnte join()die drei Threads seriell und würde dann nicht fortfahren, bis alle drei fertig sind.

  • Fragen Sie in regelmäßigen Abständen den Thread-Status der erzeugten Threads ab.

  • Lege alle gespawnten Threads in einen separaten ThreadGroupund frage den activeCount()auf ab ThreadGroupund warte, bis er auf 0 kommt.

  • Richten Sie eine benutzerdefinierte Rückruf- oder Listener-Schnittstelle für die Kommunikation zwischen Threads ein.

Ich bin mir sicher, dass es noch viele andere Möglichkeiten gibt, die mir noch fehlen.

digitaljoel
quelle
1

Hier ist eine Lösung, die einfach, kurz, leicht zu verstehen ist und perfekt für mich funktioniert. Ich musste auf den Bildschirm zeichnen, wenn ein anderer Thread endet. konnte aber nicht, weil der Haupt-Thread die Kontrolle über den Bildschirm hat. So:

(1) Ich habe die globale Variable erstellt: boolean end1 = false;Der Thread setzt sie beim Beenden auf true. Dies wird im Mainthread von der "postDelayed" -Schleife erfasst, wo darauf geantwortet wird.

(2) Mein Thread enthält:

void myThread() {
    end1 = false;
    new CountDownTimer(((60000, 1000) { // milliseconds for onFinish, onTick
        public void onFinish()
        {
            // do stuff here once at end of time.
            end1 = true; // signal that the thread has ended.
        }
        public void onTick(long millisUntilFinished)
        {
          // do stuff here repeatedly.
        }
    }.start();

}

(3) Glücklicherweise wird "postDelayed" im Hauptthread ausgeführt, sodass der andere Thread einmal pro Sekunde überprüft wird. Wenn der andere Thread endet, kann dies beginnen, was auch immer wir als nächstes tun möchten.

Handler h1 = new Handler();

private void checkThread() {
   h1.postDelayed(new Runnable() {
      public void run() {
         if (end1)
            // resond to the second thread ending here.
         else
            h1.postDelayed(this, 1000);
      }
   }, 1000);
}

(4) Starten Sie das Ganze schließlich irgendwo in Ihrem Code, indem Sie Folgendes aufrufen:

void startThread()
{
   myThread();
   checkThread();
}
DreamMaster Pro
quelle
1

Ich denke, der einfachste Weg ist, ThreadPoolExecutorKlasse zu benutzen .

  1. Es hat eine Warteschlange und Sie können festlegen, wie viele Threads parallel arbeiten sollen.
  2. Es hat nette Rückrufmethoden:

Hook-Methoden

Diese Klasse bietet geschützte überschreibbare Methoden beforeExecute(java.lang.Thread, java.lang.Runnable)und afterExecute(java.lang.Runnable, java.lang.Throwable)Methoden, die vor und nach der Ausführung jeder Aufgabe aufgerufen werden. Diese können verwendet werden, um die Ausführungsumgebung zu manipulieren. B. ThreadLocals neu initialisieren, Statistiken sammeln oder Protokolleinträge hinzufügen. Darüber hinaus kann die Methode terminated()überschrieben werden, um eine spezielle Verarbeitung durchzuführen, die ausgeführt werden muss, sobald der Executor vollständig beendet wurde.

Das ist genau das, was wir brauchen. Wir werden überschreiben afterExecute(), um Rückrufe zu erhalten, nachdem jeder Thread abgeschlossen ist, und überschreiben, um terminated()zu wissen, wann alle Threads abgeschlossen sind.

Also hier ist, was Sie tun sollten

  1. Erstellen Sie einen Executor:

    private ThreadPoolExecutor executor;
    private int NUMBER_OF_CORES = Runtime.getRuntime().availableProcessors();    
    
    
    
    private void initExecutor() {
    
    executor = new ThreadPoolExecutor(
            NUMBER_OF_CORES * 2,  //core pool size
            NUMBER_OF_CORES * 2, //max pool size
            60L, //keep aive time
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<Runnable>()
    ) {
    
        @Override
        protected void afterExecute(Runnable r, Throwable t) {
            super.afterExecute(r, t);
                //Yet another thread is finished:
                informUiAboutProgress(executor.getCompletedTaskCount(), listOfUrisToProcess.size());
            }
        }
    
    };
    
        @Override
        protected void terminated() {
            super.terminated();
            informUiThatWeAreDone();
        }
    
    }
  2. Und starten Sie Ihre Threads:

    private void startTheWork(){
        for (Uri uri : listOfUrisToProcess) {
            executor.execute(new Runnable() {
                @Override
                public void run() {
                    doSomeHeavyWork(uri);
                }
            });
        }
        executor.shutdown(); //call it when you won't add jobs anymore 
    }

Innerhalb der Methode informUiThatWeAreDone();tun Sie alles, was Sie tun müssen, wenn alle Threads fertig sind, z. B. die Benutzeroberfläche aktualisieren.

HINWEIS: Vergessen Sie nicht, zu verwendensynchronized Methoden zu verwenden, da Sie Ihre Arbeit parallel ausführen und SEHR VORSICHTIG sind, wenn Sie die synchronizedMethode von einer anderen synchronizedMethode aus aufrufen ! Dies führt häufig zu Deadlocks

Hoffe das hilft!

Kirill Karmazin
quelle
0

Schauen Sie sich die Java-Dokumentation für die Thread-Klasse an. Sie können den Status des Threads überprüfen. Wenn Sie die drei Threads in Mitgliedsvariablen einfügen, können alle drei Threads die Zustände des anderen lesen.

Sie müssen jedoch etwas vorsichtig sein, da dies zu Rennbedingungen zwischen den Threads führen kann. Versuchen Sie einfach, komplizierte Logik zu vermeiden, die auf dem Status der anderen Threads basiert. Vermeiden Sie auf jeden Fall, dass mehrere Threads in dieselben Variablen schreiben.

Don Kirkby
quelle