2010-03-26 12 views
16

Se un thread viene interrotto all'interno di Object.wait() o Thread.join(), esso genera uno InterruptedException, che ripristina lo stato interrotto del thread. . I. e, se ho un ciclo come questo all'interno di un Runnable.run():Perché interruptedExceptions cancella lo stato interrotto di un thread?

while (!this._workerThread.isInterrupted()) { 
    // do something 
    try { 
     synchronized (this) { 
      this.wait(this._waitPeriod); 
     } 
    } catch (InterruptedException e) { 
     if (!this._isStopping()) { 
      this._handleFault(e); 
     } 
    } 
} 

il filo continua a funzionare dopo la chiamata interrupt(). Ciò significa che devo uscire esplicitamente dal ciclo controllando il mio flag di stop nella condizione del loop, rilanciare l'eccezione o aggiungere uno break.

Ora, questo non è esattamente un problema, dal momento che questo comportamento è ben documentato e non mi impedisce di fare nulla nel modo che voglio. Tuttavia, non riesco a capire il concetto alla base: perché un thread non è più considerato interrotto una volta che l'eccezione è stata lanciata? Un comportamento simile si verifica anche se si ottiene lo stato interrotto con interrupted() anziché isInterrupted(), quindi, il thread verrà visualizzato solo una volta.

Sto facendo qualcosa di insolito qui? Ad esempio, è più comune catturare lo InterruptedException all'esterno del ciclo?

(Anche se io non sono esattamente un principiante, ho taggata questo "principiante", perché sembra una domanda molto semplice per me, a guardarla.)

+0

Dato che quello che chiedi è una questione di opinione ("perché è così?") Dovresti probabilmente creare questo wiki della comunità. –

+1

@ Jonathan: Non è davvero una questione di opinione. Osservando i documenti dell'API, è chiaro che dietro a questo deve esserci una logica, quindi c'è una risposta corretta alla mia domanda. Quello che ho accettato sembra molto plausibile (gestione degli interrupt sicuri dei thread). –

risposta

7

L'idea è che un interrupt debba essere gestito una volta. Se un esplicito non ha cancellato il flag "interrupt", la maggior parte dei catcher per InterruptedException dovrebbe cancellare esplicitamente tale flag. Viceversa, è possibile "non chiarire" il contrassegno mediante l'interruzione automatica (Thread.currentThread().interrupt()). I progettisti di Java hanno optato per la semantica che avrebbe risparmiato le sequenze di tasti per la maggior parte del tempo (ad esempio, più spesso si desidera eliminare la bandiera anziché mantenerla impostata).

+2

Non sono d'accordo con la logica dietro a ciò che sta succedendo, ma loro avrebbero dovuto definire il metodo in modo migliore. –

0

Questo perché un InterruptedException è considerato un anormale evento in cui qualcun altro tenta di interrompere un thread dall'esterno.

Quando si desidera interrompere realmente un thread, basta interrompere la sua condizione di ciclo impostando un valore booleano o qualcosa di simile. O si utilizza .wait() e .notify()dall'interno di tale thread. Ma se si sta facendo wait() esternamente:

  • viene generata un'eccezione per notificare che una filettatura esterna ha cercato di interrompere me o per farmi aspettare
  • il filo continua il suo lavoro, perché non prende alcun ordine da un altro thread! Ma l'aumento dell'eccezione ti consente di aggiungere una gestione speciale e fare ciò che vuoi, anche in modo efficace per fermare il thread.
+0

Questo è sbagliato. 'Thread # isInterrupted' è pensato per essere quel booleano di cui stavi parlando che uno potrebbe usare per il loop. L'interruzione di un thread imposta questo bit, ma poi catturare "InterruptedException" lo cancella e questo è ciò di cui trattava questa domanda. Se hai il pieno controllo del thread, va bene dato che decidi cosa fare con il tuo thread, ma se stai scrivendo una libreria o un codice di utilità che non esegue alcuna gestione dei thread e viene semplicemente chiamato da un altro codice, è modi molto cattivi per ripristinare il bit interrotto che qualcuno ha appena impostato. – mpontes

1

scrivere il codice come questo e non avrà bisogno di una bandiera:

try { 
    while (!this._workerThread.isInterrupted()) { 
     // do something 
     synchronized (this) { 
      this.wait(this._waitPeriod); 
     } 
     // do something else 
    } 
} catch (InterruptedException e) { 
    // ignore ... 
} 

Come @Boyan fa notare, si tratta di una cattiva idea di schiacciare che l'eccezione di interruzione ... in generale. In questo caso, il contesto determinerà se è necessario schiacciarlo (come sopra), impostare il flag di interrupt (di nuovo) o consentire la propagazione dell'eccezione. Tra le altre cose, dipende da cosa significa l'interrupt nella/dalla tua applicazione.

+0

non dovresti semplicemente ignorarlo. Nel 90% dei casi starai perfettamente con il codice che hai scritto, ma c'è una possibilità che qualcun altro stia aspettando questa interruzione e cancellare la bandiera infrangerà la loro logica. Quindi, per sicurezza, devi chiamare 'Thread.currentThread(). Interrupt()' nel gestore delle eccezioni. – Boyan

0

Non dovrebbe.Si tratta di uno sfortunato difetto di progettazione che fa affidamento su interruzioni un'attività rischiosa, poiché troppo spesso il codice libreria cattura lo InterruptedException senza reimpostare il flag interrotto del thread e proseguire. Se ti capita di segnalare un'interruzione al thread quando quel particolare pezzo di codice libreria rotto è in esecuzione, quando il tuo codice riacquista il controllo dell'esecuzione, sarà lasciato senza indizio che l'interruzione è avvenuta.

Questo deve accadere solo una volta in qualsiasi posto che si sta chiamando dal codice, quindi per poter interrompere un thread e quindi utilizzare il bit interrotto per controllare il flusso dall'interno di detto thread in modo sicuro, è necessario essere sicuri al 100% che ogni pezzo di codice che stai chiamando non cancelli per errore il bit interrotto. Questo è molto difficile da fare quando sono coinvolte le librerie, ma anche se si potesse tenere conto di ogni singola libreria che si sta utilizzando nel codice, ciò non tiene ancora conto di buggy JRE code that can make the same mistake.

Il fatto che un autore di una libreria (o JRE!) Non si preoccupi o non pensi alle interruzioni per rompere la logica del codice che lo richiede mostra che questa è l'azione predefinita sbagliata da intraprendere. Qualcuno a cui non interessa il bit interrotto del thread probabilmente non si preoccuperà di ripristinarlo dopo aver catturato InterruptedException - forse non sanno nemmeno che esiste! Se la cattura di InterruptedException non ha ripristinato lo stato interrotto del thread, quindi chiunque non fosse a conoscenza del bit interrotto automaticamente "fa la cosa giusta" e non causa un problema per qualsiasi codice di chiamata che si basa su interruzioni. Chiunque richiedesse la cancellazione potrebbe ancora farlo manualmente, ma sarebbe un'azione esplicita che è molto più probabile che sia corretta rispetto a un effetto collaterale solitamente non desiderato di catturare l'eccezione controllata InterruptedException. Allo stato attuale, se ti affidi al bit interrotto del thread, chiunque scenda il tuo stack chiamante che chiama Thread.sleep() distrattamente può potenzialmente rovinare la tua giornata.

Di conseguenza, la maggior parte del codice multi-thread Java duplica semplicemente il modello di interrupt thread Java con un campo di istanza "isRunning" e un meccanismo per capovolgerlo come soluzione alternativa.

Problemi correlati