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?
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?
Risposte:
Esistono diversi modi per farlo:
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à NotifyingThread
e 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 ThreadCompleteListener
e 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 notifyOfThreadComplete
metodo verrà richiamato con l'istanza di thread appena completata (o arrestata in modo anomalo).
Nota che sarebbe meglio implements Runnable
piuttosto che extends Thread
per NotifyingThread
come l'estensione del thread è di solito scoraggiata nel nuovo codice. Ma sto programmando la tua domanda. Se cambi la NotifyingThread
classe da implementare, Runnable
allora devi cambiare parte del tuo codice che gestisce i thread, il che è abbastanza semplice da fare.
notify
metodo non insinde il run
metodo, ma dopo.
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.
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' Runnable
interfaccia 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 CallbackRunnable
interfaccia è 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.concurrent
se il tuo progetto lo consente.
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?
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.
Vuoi aspettare che finiscano? In tal caso, utilizzare il metodo Join.
C'è anche la proprietà isAlive se vuoi solo controllarla.
Thread
a start
, cosa fa il thread, ma la chiamata ritorna immediatamente. isAlive
dovrebbe essere un semplice test di bandiera, ma quando ho cercato su Google il metodo era native
.
È 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.
È inoltre possibile utilizzare l' Executors
oggetto per creare un pool di thread ExecutorService . Quindi utilizzare il invokeAll
metodo 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 awaitTermination
per bloccare fino a quando il pool non è stato eseguito. Assicurati di chiamare shutdown
() quando hai finito di aggiungere attività.
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.
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
CountDownLatch
viene inizializzato con un determinato conteggio. I metodi await si bloccano fino a quando il conteggio corrente non raggiunge lo zero a causa di invocazioni delcountDown()
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 Future
le attività da presentare una proposta in ExecutorService
e controllare lo stato di blocco di chiamata get()
in Future
oggetto
Dai un'occhiata alle relative domande SE:
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 ThreadGroup
e interroga il activeCount()
su ThreadGroup
e 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.
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();
}
Immagino che il modo più semplice sia usare la ThreadPoolExecutor
classe.
Metodi di aggancio
Questa classe fornisce l'overridable protetto
beforeExecute(java.lang.Thread, java.lang.Runnable)
e iafterExecute(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 metodoterminated()
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
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();
}
}
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 synchronized
metodi poiché svolgi il tuo lavoro in parallelo e SIA MOLTO ATTENZIONE se decidi di chiamare il synchronized
metodo da un altro synchronized
metodo! Questo porta spesso a deadlock
Spero che questo ti aiuti!
È inoltre possibile utilizzare SwingWorker, che ha il supporto integrato per la modifica delle proprietà. Vedere addPropertyChangeListener () o il metodo get () per un esempio di listener di modifica dello stato.
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.