Gestione di InterruptedException in Java


317

Qual è la differenza tra i seguenti modi di gestione InterruptedException? Qual'è il miglior modo di 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 vengono utilizzati questi due.


Risposte:


436

Qual è la differenza tra i seguenti modi di gestire InterruptedException? Qual'è il miglior modo di farlo?

Probabilmente sei venuto a porre questa domanda perché hai chiamato un metodo che lancia InterruptedException.

Prima di tutto, dovresti vedere throws InterruptedExceptionper 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? Puoi capire la risposta pensando a quanto segue:

Ha senso lanciare un metodo che stai implementando InterruptedException? In altre parole, è un InterruptedExceptionrisultato sensato quando si chiama il metodo?

  • Se , allora throws InterruptedExceptiondovrebbe far parte della firma del tuo metodo e dovresti far propagare l'eccezione (cioè non prenderla affatto).

    Esempio : il metodo attende un valore dalla rete per completare il calcolo e restituire un risultato. Se la chiamata di rete bloccante genera un InterruptedExceptionmetodo, non è possibile completare il calcolo in modo normale. Hai lasciato InterruptedExceptionpropagare.

    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 throws InterruptedExceptione dovresti (devi!) Cogliere l'eccezione. Ora due cose sono importanti da tenere a mente in questa situazione:

    1. Qualcuno ha interrotto il tuo thread. Che qualcuno è probabilmente desideroso di annullare l'operazione, terminare il programma con garbo o altro. Dovresti essere educato con quella persona e tornare dal tuo metodo senza ulteriori indugi.

    2. Anche se il tuo metodo è in grado di produrre un valore di ritorno sensato nel caso in cui InterruptedExceptionil thread sia stato interrotto, può essere ancora importante. In particolare, il codice che chiama il metodo potrebbe essere interessato a stabilire se si è verificata un'interruzione durante l'esecuzione del metodo. Dovresti quindi registrare il fatto che si è verificata un'interruzione impostando il flag interrotto:Thread.currentThread().interrupt()

    Esempio : l'utente ha chiesto di stampare una somma di due valori. La stampa " 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 dello stack a causa di un InterruptedException). In altre parole, non ha senso dichiarare questo metodo con throws 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 il chiamante. Potresti inventare una nuova eccezione di runtime ma la causa principale (qualcuno vuole che il thread interrompa l'esecuzione) potrebbe perdersi.

Altri esempi:

ImplementazioneRunnable : Come forse avrai scoperto, la firma di Runnable.runnon consente il rilancio InterruptedExceptions. Bene, ti sei iscritto all'implementazione Runnable, il che significa che ti sei registrato per occuparti di possibili InterruptedExceptions. Scegli un'interfaccia diversa, ad esempio Callable, oppure segui il secondo approccio sopra.

 

ChiamataThread.sleep : stai tentando di leggere un file e la specifica dice che dovresti provare 10 volte con 1 secondo tra. Tu chiami Thread.sleep(1000). Quindi, devi affrontare InterruptedException. Per un metodo come tryToReadFileha perfettamente senso dire "Se sono interrotto, non posso completare la mia azione di provare a leggere il file" . In altre parole, ha perfettamente senso lanciare il metodo InterruptedExceptions.

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 .


Qual è il problema con il secondo metodo?
blitzkriegz,

4
L'articolo dice che dovresti chiamare interrupt()per preservare lo stato interrotto. Qual è il motivo dietro non farlo su un Thread.sleep()?
Oksayt,

7
Non sono d'accordo, nel caso in cui il tuo thread non supporti gli interrupt, qualsiasi interruzione ricevuta indica una condizione imprevista (cioè un errore di programmazione, un cattivo utilizzo dell'API) e il salvataggio con RuntimeException è una risposta appropriata (fail-fast ). A meno che non sia considerato parte del contratto generale di un thread che anche se non si supportano gli interrupt, è necessario continuare l'operazione quando li si riceve.
amoe

12
Chiamare metodi che generano InterruptedExceptions e dicono che "non supportano gli interrupt" sembra una programmazione sciatta e un bug in attesa di accadere. Metti diversamente; Se si chiama un metodo che è dichiarato a buttare InterruptedException dovrebbe non essere una condizione imprevista se tale metodo in effetti gettare tale eccezione.
aioobe,

1
@MartiNito, no, la chiamata Thread.currentThread.interrupt()in un InterruptedExceptionblocco catch imposterà solo il flag di interrupt. Non causerà il InterruptedExceptionlancio / cattura di un altro in un InterruptedExceptionblocco di cattura esterno .
aioobe,

98

Sta succedendo che stavo leggendo questa mattina sul mio modo di lavorare in Java Concurrency In Practice di Brian Goetz. Fondamentalmente dice che dovresti fare una delle tre cose

  1. Propaga ilInterruptedException - Dichiara il tuo metodo per lanciare il segno di spunta in InterruptedExceptionmodo che il tuo chiamante debba gestirlo.

  2. Ripristina l'interruzione - A volte non puoi lanciare InterruptedException. In questi casi è necessario acquisire InterruptedExceptione ripristinare lo stato di interruzione chiamando il interrupt()metodo in currentThreadmodo che il codice più in alto nello stack di chiamate possa vedere che è stata emessa un'interruzione e tornare rapidamente dal metodo. Nota: questo è applicabile solo quando il metodo ha una semantica "try" o "best effort", ovvero non accadrebbe nulla di critico se il metodo non raggiungesse il suo obiettivo. Ad esempio, log()o sendMetric()può essere tale metodo, oppure boolean tryTransferMoney(), ma non void transferMoney(). Vedi qui per maggiori dettagli.

  3. Ignora l'interruzione all'interno del metodo, ma ripristina lo stato all'uscita , ad esempio tramite Guava Uninterruptibles. Uninterruptiblesassumere il codice del boilerplate come nell'esempio Attività nonancorabili in JCIP § 7.1.3.

10
"A volte non puoi lanciare InterruptedException" - direi, a volte non è appropriato per il metodo di propagare InterruptedExceptions. La tua formulazione sembra suggerire che dovresti ricrescere InterruptedException ogni volta che puoi .
aioobe,

1
E se riesci a leggere il capitolo 7, vedrai che ci sono alcune sottigliezze, come quelle nel Listato 7.7, in cui un'attività non negoziabile non ripristina immediatamente l'interrupt , ma solo dopo che è stata eseguita. Anche Goetz ha molti coautori di quel libro ...
Fizz,

1
Mi piace la tua risposta perché è concisa, tuttavia credo che dovrebbe anche affermare di tornare delicatamente e rapidamente dalla tua funzione nel secondo caso. Immagina un ciclo lungo (o infinito) con un Thread.sleep () nel mezzo. La cattura di InterruptedException dovrebbe chiamare Thread.currentThread (). Interrupt () ed uscire dal ciclo.
Yann Vo,

@YannVo Ho modificato la risposta per applicare il tuo suggerimento.
leventov,

19

Cosa stai cercando di fare?

La InterruptedExceptionviene generata quando un thread è in attesa o dormire e un'altra interrupt filo utilizzando il interruptmetodo della classe Thread. Quindi, se si rileva questa eccezione, significa che il thread è stato interrotto. Di solito non ha senso richiamare di Thread.currentThread().interrupt();nuovo, a meno che non si desideri controllare lo stato "interrotto" del thread da qualche altra parte.

Per quanto riguarda la tua altra opzione di lanciare un RuntimeException, non sembra una cosa molto saggia da fare (chi lo catturerà? Come verrà gestito?), Ma è difficile dire di più senza ulteriori informazioni.


14
La chiamata Thread.currentThread().interrupt()imposta il flag interrotto (di nuovo), il che è davvero utile se vogliamo garantire che l'interrupt venga notato ed elaborato a un livello superiore.
Péter Török,

1
@Péter: stavo solo aggiornando la risposta per menzionarlo. Grazie.
Grodriguez,

12

Per me la cosa fondamentale di questo è: una InterruptedException non è qualcosa che non va, è il thread che fa ciò che gli hai detto di fare. Quindi riproporlo racchiuso in una RuntimeException non ha senso.

In molti casi ha senso riproporre un'eccezione racchiusa in 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 premere qualunque gestore di eccezioni a livello di applicazione che ho in modo che possa registrarlo. Questo non è il caso di InterruptedException, è solo il thread che risponde ad aver chiamato interrupt (), sta lanciando InterruptedException per aiutare a cancellare l'elaborazione del thread in modo tempestivo.

Quindi propagare InterruptedException o mangiarlo in modo intelligente (ovvero in un luogo in cui avrà realizzato ciò che doveva fare) e ripristinare il flag di interrupt. Si noti che il flag di interrupt viene cancellato quando viene generata l'interruzione di InterruptedException; il presupposto 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 ti aspetti che il thread venga effettivamente interrotto e interromperlo equivale a un errore.

Ecco una risposta che ho scritto descrivendo come funzionano gli interrupt, con un esempio . Nel codice di esempio puoi vedere dove sta usando InterruptedException per uscire da un ciclo while nel metodo run di Runnable.


10

La scelta predefinita corretta è aggiungere InterruptedException all'elenco dei tiri. Un interrupt indica che un altro thread desidera che il thread termini. 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 si tratti solo di un arresto amichevole e tutto ciò che evita tale arresto è una risposta non amichevole.

Java non genererà casualmente InterruptedException, tutti i consigli non influiranno sulla tua applicazione, ma ho riscontrato un caso in cui lo sviluppatore seguendo la strategia "rondine" è diventato molto scomodo. Una squadra ha sviluppato una vasta serie di test e ha usato Thread.Sleep 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. A peggiorare la situazione, quando si è tentato di annullare il lavoro CI, non è mai stato chiuso perché il Thread.Interrupt che intendeva interrompere il test non ha interrotto il lavoro. Abbiamo dovuto accedere al box e terminare manualmente i processi.

Per farla breve, se semplicemente lanci InterruptedException stai abbinando l'intento predefinito che il tuo thread dovrebbe finire. Se non riesci ad aggiungere InterruptedException alla tua lista dei tiri, lo avvolgo in RuntimeException.

Esiste un argomento molto razionale secondo cui 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 si verifica una InterruptedException perché c'è un errore nel tuo codice, (ad esempio loop infinito, dead-lock), e Interrupt è un altro metodo di thread per gestire quell'errore.

Se sai che c'è una pulizia razionale da fare, allora fallo. Se conosci una causa più profonda per l'interruzione, puoi assumere una gestione più completa.

Quindi, in sintesi, le tue scelte di gestione dovrebbero seguire questo elenco:

  1. Per impostazione predefinita, aggiungi ai tiri.
  2. Se non ti è permesso aggiungere ai tiri, lancia RuntimeException (e). (Migliore scelta di più opzioni errate)
  3. Solo quando conosci una causa esplicita dell'interruzione, gestisci come desideri. Se la tua gestione è locale al tuo metodo, reimposta interrotto da una chiamata a Thread.currentThread (). Interrupt ().

3

Volevo solo aggiungere un'ultima opzione a ciò che menziona la maggior parte delle persone e degli articoli. Come ha affermato mR_fr0g, è importante gestire correttamente l'interrupt sia:

  • Propagazione di InterruptException

  • Ripristina lo stato di interruzione sul thread

O inoltre:

  • Gestione personalizzata di Interrupt

Non c'è nulla di sbagliato nel gestire l'interrupt in modo personalizzato a seconda delle circostanze. Poiché un interrupt è 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 grazia. Ad esempio, se un thread è in attesa, in attesa di IO o di una risposta hardware, quando riceve l'interruzione, è perfettamente valido chiudere con garbo 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/


0

Direi che in alcuni casi va bene non fare nulla. Probabilmente non è qualcosa che dovresti fare per impostazione predefinita, ma nel caso in cui non ci sia modo per l'interruzione, non sono sicuro di cos'altro fare (probabilmente errore di registrazione, ma ciò non influisce sul flusso del programma).

Un caso potrebbe essere nel caso tu abbia una coda di attività (blocco). Nel caso in cui un thread demone gestisca queste attività e non si interrompa il thread da soli (per quanto ne sappia, jvm non interrompe i thread demone all'arresto di jvm), non vedo alcun modo che si verifichi l'interruzione, e quindi potrebbe essere appena ignorato. (So ​​che un thread demone può essere ucciso da jvm in qualsiasi momento e quindi non è adatto in alcuni casi).

EDIT: un altro caso potrebbe essere un blocco protetto, almeno basato sul tutorial di Oracle all'indirizzo: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html


Questo è un cattivo consiglio. Ho dei problemi estremi a pensare a qualsiasi compito così importante da ignorare gli Interrupt. Oracle era probabilmente solo pigro e non voleva aggiungere (più) disordine all'invocazione di Thread.sleep (). A meno che tu non sappia perché il tuo thread viene interrotto, dovresti interrompere qualsiasi cosa tu stia facendo (o restituisci o ripeti un'eccezione per aiutare il thread a morire il più velocemente possibile).
Ajax,

Ecco perché ho detto a volte. Anche perché non è stato ancora suggerito ed è perfettamente a mio avviso in alcuni casi. Immagino che dipenda dal tipo di software che stai costruendo, ma se hai un thread di lavoro 24/7 che non dovrebbe mai fermarsi (a parte la chiusura di JVM), tratterei gli interruzioni dal prendere un oggetto da una BlockingQueue come un errore (es. registro) poiché "non dovrebbe accadere" e riprovare. Ovviamente avrei bandiere separate per controllare la chiusura del programma. In alcuni dei miei programmi la chiusura di JVM non è qualcosa che dovrebbe accadere durante il normale funzionamento.
Bjarne Boström,

O in altri termini: a mio avviso è meglio rischiare di avere un'istruzione di registro degli errori aggiuntiva (e ad esempio avere posta inviata sui registri degli errori) piuttosto che avere un'applicazione che dovrebbe essere in esecuzione 24 ore su 24, 7 giorni su 7, per fermarsi a causa di un'intercettazione Non vedo alcun modo di accadere in condizioni normali.
Bjarne Boström,

Hm, beh, i tuoi casi d'uso potrebbero essere diversi, ma in un mondo ideale, non devi solo terminare perché sei stato interrotto. Smetti di togliere il lavoro dalla coda e lasci morire QUEL thread. Se anche lo scheduler dei thread viene interrotto e gli viene detto di spegnerlo, la JVM sta terminando e il gioco finisce. In particolare, se invii kill -9 o stai tentando in altro modo di eliminare il tuo server di posta o qualsiasi altra cosa, ti ignorerà fino a quando non forzerai la chiusura, impedendo così ad altre parti del tuo codice di arrestarsi con grazia. Forza l'uccisione mentre scrivo su disco o db, e sono sicuro che accadranno cose meravigliose.
Ajax
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.