Gestione InterruptedException in Java
Qual è la differenza tra i seguenti modi di gestire InterruptedException
? Qual è il modo migliore per farlo?
try{
//...
} catch(InterruptedException e) {
Thread.currentThread().interrupt();
}
O
try{
//...
} catch(InterruptedException e) {
throw new RuntimeException(e);
}
EDIT: mi piacerebbe anche sapere in quali scenari sono usati questi due.
7 answers
Probabilmente sei venuto a fare questa domanda perché hai chiamato un metodo che lancia InterruptedException
.
Prima di tutto, dovresti vedere throws InterruptedException
per quello che è: una parte della firma del metodo e un possibile risultato della chiamata del metodo che stai chiamando. Quindi inizia abbracciando il fatto che un InterruptedException
è un risultato perfettamente valido della chiamata al metodo.
Ora, se il metodo che stai chiamando genera tale eccezione, cosa dovrebbe fare il tuo metodo? Si può capire la risposta da pensando a quanto segue:
Ha senso per il metodo che stai implementando per lanciare un InterruptedException
? In altre parole, un InterruptedException
è un risultato ragionevole quando si chiama il proprio metodo?
-
Se sì, allora
throws InterruptedException
dovrebbe essere parte di la tua firma del metodo e dovresti lasciare che l'eccezione si propaghi (cioè non prenderla affatto).Esempio: il metodo attende un valore dal rete per completare il calcolo e restituire un risultato. Se la chiamata di rete di blocco genera un
InterruptedException
, il metodo non può terminare il calcolo in modo normale. Hai lasciato che ilInterruptedException
si propagasse.int computeSum(Server server) throws InterruptedException { // Any InterruptedException thrown below is propagated int a = server.getValueA(); int b = server.getValueB(); return a + b; }
-
Se no , allora non dovresti dichiarare il tuo metodo con
throws InterruptedException
e dovresti (devi!) cattura l'eccezione. Ora due cose sono importanti da tenere a mente in questa situazione:Qualcuno ha interrotto il tuo thread. Che qualcuno è probabilmente desideroso di annullare l'operazione, terminare il programma con grazia, o qualsiasi altra cosa. Dovresti essere educato con quel qualcuno e tornare dal tuo metodo senza ulteriori indugi.
Anche se il tuo metodo può riuscire a produrre un valore di ritorno ragionevole in caso di
InterruptedException
il fatto che il thread sia stato interrotto potrebbe ancora essere importante. In particolare, il codice che chiama il metodo potrebbe essere interessato a sapere se si è verificata un'interruzione durante l'esecuzione del metodo. Si dovrebbe quindi registrare il fatto che un'interruzione ha avuto luogo impostando il flag interrotto:Thread.currentThread().interrupt()
Esempio: L'utente ha chiesto di stampare una somma di due valori. La stampa di "
Failed to compute sum
" è accettabile se la somma non può essere calcolata (e molto meglio che lasciare che il programma si blocchi con una traccia di stack a causa di unInterruptedException
). In altre parole, non ha senso dichiarare questo metodo conthrows InterruptedException
.void printSum(Server server) { try { int sum = computeSum(server); System.out.println("Sum: " + sum); } catch (InterruptedException e) { Thread.currentThread().interrupt(); // set interrupt flag System.out.println("Failed to compute sum"); } }
Ormai dovrebbe essere chiaro che solo fare throw new RuntimeException(e)
è una cattiva idea. Non è molto gentile con chi chiama. Potresti inventare una nuova eccezione di runtime ma la causa principale (qualcuno vuole che il thread interrompa l'esecuzione) potrebbe perdersi.
Altri esempi:
Attuazione
Runnable
: Come avrai scoperto, la firma diRunnable.run
non consente di ripensareInterruptedExceptions
. Bene, tu ti sei iscritto all'implementazione diRunnable
, il che significa che tu hai firmato per affrontare possibileInterruptedExceptions
. Scegliere un'interfaccia diversa, ad esempioCallable
, oppure segui il secondo approccio sopra.
Chiamata
Thread.sleep
: Stai tentando di leggere un file e le specifiche dicono che dovresti provare 10 volte con 1 secondo in mezzo. Si chiamaThread.sleep(1000)
. Quindi, devi occuparti diInterruptedException
. Per un metodo come {[27] } ha perfettamente senso dire, " Se vengo interrotto, non riesco a completare la mia azione di cercare di leggere il file " . In altre parole, ha perfettamente senso che il metodo lanciInterruptedExceptions
.String tryToReadFile(File f) throws InterruptedException { for (int i = 0; i < 10; i++) { if (f.exists()) return readFile(f); Thread.sleep(1000); } return null; }
Questo post è stato riscritto come articolo qui.
Guarda caso stavo leggendo di questo questa mattina sul mio modo di lavorare in Java Concurrency In Practice di Brian Goetz. Fondamentalmente dice che dovresti fare una delle due cose
Propagare il
InterruptedException
- Dichiara il tuo metodo per lanciare ilInterruptedException
controllato in modo che il tuo chiamante debba gestirlo.Ripristina l'Interrupt - A volte non puoi lanciare
InterruptedException
. In questi casi si dovrebbe prendere ilInterruptedException
e ripristinare l'interrupt stato chiamando il metodointerrupt()
sulcurrentThread
in modo che il codice più in alto nello stack di chiamate possa vedere che è stato emesso un interrupt.
Cosa stai cercando di fare?
Il InterruptedException
viene lanciato quando un thread è in attesa o in sospensione e un altro thread lo interrompe usando il metodo interrupt
nella classe Thread
. Quindi, se si cattura questa eccezione, significa che il thread è stato interrotto. Di solito non ha senso chiamare di nuovo Thread.currentThread().interrupt();
, a meno che non si voglia controllare lo stato "interrotto" del thread da qualche altra parte.
Per quanto riguarda l'altra opzione di lanciare un RuntimeException
, non sembra una cosa molto saggia da fare (chi lo farà capito? come sarà gestito?) ma è difficile dire di più senza ulteriori informazioni.
Per me la cosa fondamentale di questo è: un'InterruptedException non è qualcosa che va storto, è il thread che fa quello che gli hai detto di fare. Pertanto, ripensarlo avvolto in una RuntimeException non ha senso.
In molti casi ha senso ripensare un'eccezione racchiusa in una RuntimeException quando dici, non so cosa sia andato storto qui e non posso fare nulla per risolverlo, voglio solo che esca dal flusso di elaborazione corrente e colpisca qualsiasi eccezione a livello di applicazione gestore che ho in modo che possa registrarlo. Questo non è il caso di un'InterruptedException, è solo il thread che risponde ad avere interrupt() chiamato su di esso, sta lanciando l'InterruptedException per aiutare ad annullare l'elaborazione del thread in modo tempestivo.
Quindi propagare InterruptedException o mangiarlo in modo intelligente (ovvero in un punto in cui avrà realizzato ciò che doveva fare) e reimpostare il flag di interrupt. Si noti che il flag di interrupt viene cancellato quando il InterruptedException viene generata; l'ipotesi che gli sviluppatori della libreria Jdk fanno è che catturare l'eccezione equivale a gestirla, quindi per impostazione predefinita il flag viene cancellato.
Quindi sicuramente il primo modo è migliore, il secondo esempio pubblicato nella domanda non è utile a meno che non ci si aspetti che il thread venga effettivamente interrotto e l'interruzione equivale a un errore.
Ecco una risposta che ho scritto che descrive come funzionano gli interrupt, con un esempio . Si può vedere nel codice di esempio in cui utilizza InterruptedException per salvare un ciclo while nel metodo Runnable.
La scelta predefinita corretta è aggiungi InterruptedException alla tua lista di lanci. Un Interrupt indica che un altro thread desidera che il thread finisca. Il motivo di questa richiesta non è reso evidente ed è del tutto contestuale, quindi se non si dispone di alcuna conoscenza aggiuntiva si dovrebbe presumere che sia solo un arresto amichevole, e tutto ciò che evita quell'arresto è una risposta non amichevole.
Java non lancerà casualmente InterruptedException, tutti i consigli non influenzeranno il tuo applicazione ma ho incontrato un caso in cui lo sviluppatore ha seguito la strategia "swallow" è diventato molto scomodo. Un team aveva sviluppato una vasta serie di test e filo utilizzato.Dormi molto. Ora abbiamo iniziato a eseguire i test nel nostro server CI e, a volte, a causa di difetti nel codice, rimanevamo bloccati in attese permanenti. Per peggiorare la situazione, quando si tenta di annullare il lavoro CI non si è mai chiuso perché il Thread.L'interruzione che aveva lo scopo di interrompere il test non ha interrotto il lavoro. Abbiamo avuto per accedere alla casella e uccidere manualmente i processi.
Per farla breve, se si lancia semplicemente InterruptedException, si corrisponde all'intento predefinito che il thread dovrebbe terminare. Se non riesci ad aggiungere InterruptedException alla tua lista di lancio, lo avvolgerei in una RuntimeException.
C'è un argomento molto razionale da fare che InterruptedException dovrebbe essere una RuntimeException stessa, poiché ciò incoraggerebbe una migliore gestione "predefinita". Non è una RuntimeException solo perché i progettisti si sono attenuti a una regola categorica che una RuntimeException dovrebbe rappresentare un errore nel codice. Poiché InterruptedException non deriva direttamente da un errore nel codice, non lo è. Ma la realtà è che spesso un'InterruptedException sorge perché c'è un errore nel tuo codice, (cioè loop infinito, dead-lock), e l'Interrupt è il metodo di qualche altro thread per gestire quell'errore.
Se sai che c'è una pulizia razionale da fare, allora fallo. Se conosci una causa più profonda per l'Interrupt, puoi assumere una gestione più completa.
Quindi in sintesi le tue scelte per la gestione dovrebbero seguire questo elenco:
- Per impostazione predefinita, aggiungi ai tiri.
- Se non è consentito aggiungere ai lanci, lanciare RuntimeException (e). (scelta migliore tra più opzioni errate)
- Solo quando si conosce una causa esplicita dell'Interrupt, gestire come desiderato. Se la gestione è locale al metodo, reimpostare interrotto da una chiamata infilare.currentThread ().interrompere().
Volevo solo aggiungere un'ultima opzione a ciò che la maggior parte delle persone e degli articoli menzionano. Come ha affermato mR_fr0g, è importante gestire correttamente l'interrupt tramite:
-
Propagazione dell'InterruptException
-
Ripristina lo stato di interrupt sul thread
O in aggiunta:
- Gestione personalizzata dell'Interrupt
Non c'è niente di sbagliato nel gestire l'interrupt in modo personalizzato a seconda delle circostanze. Come interruzione è una richiesta di terminazione, al contrario di un comando forzato, è perfettamente valido completare il lavoro aggiuntivo per consentire all'applicazione di gestire la richiesta con garbo. Ad esempio, se un thread sta dormendo, in attesa di IO o di una risposta hardware, quando riceve l'Interrupt, allora è perfettamente valido chiudere con grazia qualsiasi connessione prima di terminare il thread.
Consiglio vivamente di comprendere l'argomento, ma questo articolo è una buona fonte di informazioni: http://www.ibm.com/developerworks/java/library/j-jtp05236 /
Direi che in alcuni casi è ok non fare nulla. Probabilmente non è qualcosa che dovresti fare di default, ma nel caso in cui non ci sia modo che l'interrupt si verifichi, non sono sicuro di cos'altro fare (probabilmente errore di registrazione, ma ciò non influisce sul flusso del programma).
Un caso sarebbe nel caso in cui tu abbia una coda di attività (blocco). Nel caso in cui si disponga di un thread daemon che gestisce queste attività e non si interrompe il Thread da soli (a mia conoscenza la jvm non interrompe il demone thread sull'arresto jvm), non vedo alcun modo per l'interrupt, e quindi potrebbe essere semplicemente ignorato. (So che un thread daemon può essere ucciso dalla jvm in qualsiasi momento e quindi non è adatto in alcuni casi).
MODIFICA: Un altro caso potrebbe essere blocchi custoditi, almeno in base al tutorial di Oracle su: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html