Come sapere se altri thread sono finiti?


126

Ho un oggetto con un metodo chiamato StartDownload(), che inizia tre thread.

Come posso ricevere una notifica quando ogni thread ha terminato l'esecuzione?

C'è un modo per sapere se uno (o tutti) i thread è finito o è ancora in esecuzione?


1
Dai un'occhiata alla classe
Fortyrunner

Risposte:


228

Esistono diversi modi per farlo:

  1. Utilizzare Thread.join () nel thread principale per attendere in modo bloccante il completamento di ogni thread, oppure
  2. Controllare Thread.isAlive () in modo polling - generalmente sconsigliato - per attendere il completamento di ogni thread, oppure
  3. Unorthodox, per ogni thread in questione, chiama setUncaughtExceptionHandler per chiamare un metodo nel tuo oggetto e programma ogni thread per generare un'eccezione non rilevata al completamento, oppure
  4. Utilizzare blocchi o sincronizzatori o meccanismi da java.util.concurrent o
  5. Più ortodossi, crea un ascoltatore nel tuo thread principale, quindi programma ciascuno dei tuoi thread per dire all'ascoltatore che hanno completato.

Come implementare Idea # 5? Bene, un modo è prima di creare un'interfaccia:

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

quindi creare la seguente classe:

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();
}

e poi ciascuno dei tuoi thread si estenderà NotifyingThreade invece di implementarlo run()verrà implementato doRun(). Pertanto, una volta completato, avviseranno automaticamente chiunque sia in attesa di notifica.

Infine, nella tua classe principale - quella che avvia tutti i thread (o almeno l'oggetto in attesa di notifica) - modifica quella classe in implement ThreadCompleteListenere immediatamente dopo aver creato ogni thread si aggiunge all'elenco dei listener:

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

quindi, all'uscita di ogni thread, il notifyOfThreadCompletemetodo verrà richiamato con l'istanza di thread appena completata (o arrestata in modo anomalo).

Nota che sarebbe meglio implements Runnablepiuttosto che extends Threadper NotifyingThreadcome l'estensione del thread è di solito scoraggiata nel nuovo codice. Ma sto programmando la tua domanda. Se cambi la NotifyingThreadclasse da implementare, Runnableallora devi cambiare parte del tuo codice che gestisce i thread, il che è abbastanza semplice da fare.


Ciao!! Mi piace l'ultima idea. Devo implementare un ascoltatore per farlo? Grazie
Ricardo Felgueiras,

4
ma usando questo approccio, notifiyListeners viene chiamato all'interno del run (), quindi verrà chiamato insisde il thread e le ulteriori chiamate verranno eseguite anche lì, non è così?
Jordi Puigdellívol,

1
@Eddie Jordi stava chiedendo, se è possibile chiamare il notifymetodo non insinde il runmetodo, ma dopo.
Tomasz Dzięcielewski,

1
La domanda è davvero: come si fa a disattivare il thread secondario ora. So che è finito, ma come posso accedere al thread principale ora?
Patrick,

1
Questo thread è sicuro? Sembra che NotifyListeners (e quindi notificationOfThreadComplete) verrà chiamato all'interno di NotifyingThread, piuttosto che all'interno del thread che ha creato il Listener stesso.
Aaron

13

Soluzione utilizzando 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 : viene creata una barriera ciclica con un numero di parti pari al numero di thread in esecuzione più uno per il thread principale di esecuzione (in cui viene eseguito startDownload ())

etichetta 1 - n-es DownloadingThread entra nella sala d'attesa

etichetta 3 - NUMBER_OF_DOWNLOADING_THREADS sono entrati nella sala d'attesa. Il thread principale di esecuzione li rilascia per iniziare a fare i loro lavori di download più o meno nello stesso tempo

etichetta 4 - il thread principale di esecuzione entra nella sala d'attesa. Questa è la parte "più complicata" del codice da capire. Non importa quale thread entrerà nella sala d'aspetto per la seconda volta. È importante che qualunque thread entri nella stanza per ultimo assicuri che tutti gli altri thread di download abbiano terminato i loro lavori di download.

label 2 - n-es DownloadingThread ha terminato il suo processo di download ed entra nella sala d'attesa. Se è l'ultimo, ovvero NUMBER_OF_DOWNLOADING_THREADS lo ha inserito, incluso il thread di esecuzione principale, il thread principale continuerà la sua esecuzione solo quando tutti gli altri thread avranno terminato il download.


9

Dovresti davvero preferire una soluzione che usi java.util.concurrent. Trova e leggi Josh Bloch e / o Brian Goetz sull'argomento.

Se non stai usando java.util.concurrent.*e ti stai assumendo la responsabilità dell'uso diretto dei thread, allora dovresti probabilmente usare join()per sapere quando un thread è terminato. Ecco un meccanismo di callback super semplice. Prima estendi l' Runnableinterfaccia per avere un callback:

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

Quindi crea un Executor che eseguirà il tuo eseguibile e ti richiamerà al termine.

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();
        }
    }

}

L'altra cosa ovvia da aggiungere alla tua CallbackRunnableinterfaccia è un mezzo per gestire eventuali eccezioni, quindi forse metti una public void uncaughtException(Throwable e);linea lì e nel tuo esecutore, installa un Thread.UncaughtExceptionHandler per inviarti a quel metodo di interfaccia.

Ma fare tutto ciò che inizia davvero a puzzare java.util.concurrent.Callable. Dovresti davvero considerare l'utilizzo java.util.concurrentse il tuo progetto lo consente.


Non sono abbastanza chiaro su cosa ottieni da questo meccanismo di callback rispetto alla semplice chiamata runner.join()e quindi a qualsiasi codice desideri dopo, poiché sai che il thread è terminato. È solo che puoi definire quel codice come una proprietà del runnable, in modo da poter avere cose diverse per runnable diversi?
Stephen,

2
Sì, runner.join()è il modo più diretto di aspettare. Supponevo che l'OP non volesse bloccare il thread di chiamate principale poiché avevano chiesto di essere "notificati" per ogni download, che poteva essere completato in qualsiasi ordine. Ciò ha offerto un modo per ricevere notifiche in modo asincrono.
broc.seib,

4

Vuoi aspettare che finiscano? In tal caso, utilizzare il metodo Join.

C'è anche la proprietà isAlive se vuoi solo controllarla.


3
Nota che isAlive restituisce false se il thread non ha ancora iniziato l'esecuzione (anche se il tuo thread ha già chiamato start su di esso).
Tom Hawtin: affronta il

@ TomHawtin-tackline ne sei abbastanza sicuro? Contraddirebbe la documentazione Java ("Un thread è vivo se è stato avviato e non è ancora morto" - docs.oracle.com/javase/6/docs/api/java/lang/… ). Inoltre contraddirebbe le risposte qui ( stackoverflow.com/questions/17293304/… )
Stephen

@Stephen È da tanto che non l'ho scritto, ma sembra essere vero. Immagino che abbia causato problemi ad altre persone che erano freschi nella mia memoria nove anni fa. Ciò che è osservabile dipenderà dall'implementazione. Dite a Threada start, cosa fa il thread, ma la chiamata ritorna immediatamente. isAlivedovrebbe essere un semplice test di bandiera, ma quando ho cercato su Google il metodo era native.
Tom Hawtin: affronta il

4

È possibile interrogare l'istanza del thread con getState () che restituisce un'istanza dell'enumerazione Thread.State con uno dei seguenti valori:

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

Comunque penso che sarebbe un progetto migliore avere un thread master che aspetta che i 3 figli finiscano, il master continuerà quindi l'esecuzione quando gli altri 3 avranno terminato.


L'attesa dell'uscita dei 3 bambini potrebbe non rientrare nel paradigma di utilizzo. Se si tratta di un download manager, potrebbero voler avviare 15 download e semplicemente rimuovere lo stato dalla barra di stato o avvisare l'utente quando il download è stato completato, nel qual caso un callback funzionerebbe meglio.
digitaljoel

3

È inoltre possibile utilizzare l' Executorsoggetto per creare un pool di thread ExecutorService . Quindi utilizzare il invokeAllmetodo per eseguire ciascuno dei thread e recuperare i futures. Questo si bloccherà fino a quando tutti non avranno terminato l'esecuzione. L'altra opzione sarebbe quella di eseguirle tutte usando il pool e quindi chiamare awaitTerminationper bloccare fino a quando il pool non è stato eseguito. Assicurati di chiamare shutdown() quando hai finito di aggiungere attività.


2

Molte cose sono state cambiate negli ultimi 6 anni sul fronte multi-threading.

Invece di usare join()e bloccare l'API, puoi usare

1. API ExecutorService invokeAll()

Esegue le attività assegnate, restituendo un elenco di Futures che mantengono il loro stato e i risultati una volta completati.

2. CountDownLatch

Un aiuto per la sincronizzazione che consente ad uno o più thread di attendere fino al completamento di una serie di operazioni eseguite in altri thread.

A CountDownLatchviene inizializzato con un determinato conteggio. I metodi await si bloccano fino a quando il conteggio corrente non raggiunge lo zero a causa di invocazioni del countDown()metodo, dopodiché vengono rilasciati tutti i thread in attesa e le successive invocazioni di wait restituiscono immediatamente. Questo è un fenomeno one-shot - il conteggio non può essere resettato. Se è necessaria una versione che reimposta il conteggio, prendere in considerazione l'utilizzo di CyclicBarrier.

3. ForkJoinPool o newWorkStealingPool()in Executors è un altro modo

4.Iterate attraverso tutte Futurele attività da presentare una proposta in ExecutorServicee controllare lo stato di blocco di chiamata get()in Futureoggetto

Dai un'occhiata alle relative domande SE:

Come aspettare un thread che genera il proprio thread?

Executors: come attendere in modo sincrono il completamento di tutte le attività se le attività vengono create in modo ricorsivo?


2

Suggerirei di guardare il javadoc per la classe Thread .

Hai più meccanismi per la manipolazione del thread.

  • Il thread principale potrebbe join()eseguire i tre thread in serie e quindi non procederebbe fino a quando non sono stati eseguiti tutti e tre.

  • Interroga lo stato del thread dei thread generati a intervalli.

  • Metti tutti i thread generati in un separato ThreadGroupe interroga il activeCount()su ThreadGroupe attendi che arrivi a 0.

  • Imposta un callback personalizzato o un tipo di listener di interfaccia per la comunicazione tra thread.

Sono sicuro che ci sono molti altri modi in cui mi manca ancora.


1

Ecco una soluzione semplice, breve, facile da capire e che funziona perfettamente per me. Avevo bisogno di disegnare sullo schermo quando termina un altro thread; ma non è possibile perché il thread principale ha il controllo dello schermo. Così:

(1) Ho creato la variabile globale: boolean end1 = false;il thread lo imposta su true al termine. Questo viene raccolto nel mainthread dal loop "postDelayed", dove viene risposto.

(2) Il mio thread contiene:

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) Fortunatamente, "postDelayed" viene eseguito nel thread principale, quindi è lì che controlla l'altro thread una volta al secondo. Quando termina l'altro thread, questo può iniziare qualunque cosa vogliamo fare dopo.

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) Infine, avvia l'intera operazione da qualche parte nel tuo codice chiamando:

void startThread()
{
   myThread();
   checkThread();
}

1

Immagino che il modo più semplice sia usare la ThreadPoolExecutorclasse.

  1. Ha una coda e puoi impostare quanti thread dovrebbero funzionare in parallelo.
  2. Ha dei bei metodi di callback:

Metodi di aggancio

Questa classe fornisce l'overridable protetto beforeExecute(java.lang.Thread, java.lang.Runnable)e i afterExecute(java.lang.Runnable, java.lang.Throwable)metodi che vengono chiamati prima e dopo l'esecuzione di ogni attività. Questi possono essere usati per manipolare l'ambiente di esecuzione; ad esempio, reinizializzare ThreadLocals, raccogliere statistiche o aggiungere voci di registro. Inoltre, il metodo terminated()può essere ignorato per eseguire qualsiasi elaborazione speciale che deve essere eseguita una volta che Executor è stato completamente chiuso.

che è esattamente ciò di cui abbiamo bisogno. Sostituiremo afterExecute()per ottenere i callback dopo che ogni thread è terminato e sostituiremo terminated()per sapere quando tutti i thread sono terminati.

Quindi ecco cosa dovresti fare

  1. Crea un esecutore:

    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. E inizia i tuoi thread:

    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 
    }

Il metodo Inside informUiThatWeAreDone();fa tutto ciò che è necessario fare quando tutti i thread sono terminati, ad esempio aggiornare l'interfaccia utente.

NOTA: non dimenticare di usare i synchronizedmetodi poiché svolgi il tuo lavoro in parallelo e SIA MOLTO ATTENZIONE se decidi di chiamare il synchronizedmetodo da un altro synchronizedmetodo! Questo porta spesso a deadlock

Spero che questo ti aiuti!



0

Guarda la documentazione Java per la classe Thread. Puoi controllare lo stato del thread. Se si inseriscono i tre thread nelle variabili membro, tutti e tre i thread possono leggere gli stati degli altri.

Devi stare un po 'attento, però, perché puoi causare condizioni di competizione tra i thread. Cerca solo di evitare logiche complicate basate sullo stato degli altri thread. Evita decisamente più thread che scrivono nelle stesse variabili.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.