Domanda

Un compagno di squadra ha fatto la seguente affermazione:

"Thread.interrupt() è intrinsecamente rotto e non dovrebbe (quasi) mai essere utilizzato".

Sto cercando di capire perché è così.

È una best practice nota da non utilizzare mai Thread.interrupt()?Puoi fornire prove del motivo per cui è rotto/bacato e non dovrebbe essere utilizzato per scrivere un robusto codice multithread?

Nota - Non mi interessa questa domanda se è "carina" da un conservante di design.La mia domanda è: è difettoso?

È stato utile?

Soluzione

Versione breve:

È una buona pratica nota non quella di non usare Thread.interrupt()?

NO.

Potete fornire prova del motivo per cui è rotto / passeggino, e non deve essere usato per scrivere codice multithread robusto?

È vero il contrario:è fondamentale per il codice multithread.

Vedere il Listato 7.7 in Concorrenza Java in pratica per un esempio.

Versione più lunga:

Da queste parti utilizziamo questo metodo in un luogo specifico:gestione InterruptedExceptions.Può sembrare un po' strano, ma ecco come appare nel codice:

try {
    // Some code that might throw an InterruptedException.  
    // Using sleep as an example
    Thread.sleep(10000);
} catch (InterruptedException ie) {
    System.err.println("Interrupted in our long run.  Stopping.");
    Thread.currentThread().interrupt();
}

Questo fa due cose per noi:

  1. Evita di mangiare l'eccezione di interruzione.I gestori di eccezioni automatiche IDE ti forniscono sempre qualcosa di simile ie.printStackTrace(); e un allegro "TODO:Qualcosa di utile deve andare qui!" commento.
  2. Ripristina lo stato di interruzione senza forzare un'eccezione controllata su questo metodo.Se la firma del metodo che stai implementando non ha un file throws InterruptedException clausola, questa è l'altra opzione per propagare lo stato interrotto.

Un commentatore ha suggerito che dovrei usare un'eccezione non controllata "per forzare il thread a morire". Questo presuppone che io abbia una conoscenza preliminare del fatto che uccidere bruscamente il thread sia la cosa giusta da fare.Io non.

Per citare Brian Goetz di JCIP nella pagina prima dell'elenco sopra citato:

Un'attività non deve presupporre nulla sulla politica di interruzione del relativo thread in esecuzione, a meno che non sia progettato in modo esplicito per l'esecuzione all'interno di un servizio con un criterio di interruzione specifico.

Ad esempio, immagina di aver fatto questo:

} catch (InterruptedException ie) {
    System.err.println("Interrupted in our long run.  Stopping.");
    // The following is very rude.
    throw new RuntimeException("I think the thread should die immediately", ie);
}

Dichiarerei che, indipendentemente dagli altri obblighi del resto dello stack di chiamate e dello stato associato, questo thread deve morire in questo momento.Cercherei di superare di nascosto tutti gli altri blocchi di cattura e il codice di pulizia dello stato per arrivare direttamente alla morte del thread.Peggio ancora, avrei consumato lo stato interrotto del thread.La logica a monte ora dovrebbe decostruire la mia eccezione per cercare di capire se si è verificato un errore logico del programma o se sto cercando di nascondere un'eccezione controllata all'interno di un wrapper oscurante.

Ad esempio, ecco cosa dovrebbero fare immediatamente tutti gli altri membri del team:

try {
    callBobsCode();
} catch (RuntimeException e) { // Because Bob is a jerk
    if (e.getCause() instanceOf InterruptedException) {
        // Man, what is that guy's problem?
        interruptCleanlyAndPreserveState();
        // Restoring the interrupt status
        Thread.currentThread().interrupt();
    }
}

Lo stato interrotto è più importante di qualsiasi specifico InterruptException.Per un esempio specifico del perché, vedere javadoc for Thread.interrupt():

Se questo thread è bloccato in una chiamata di wait(), wait(long), o wait(long, int) della classe Object, o del metodo join(), join(long), join(long, int), sleep(long) o sleep(long, int), metodi di questa classe, allora il suo stato di interrupt verrà cancellato e lo farà ricevere un'eccezione InterruptedException.

Come puoi vedere, è possibile creare e gestire più di una InterruptedException durante l'elaborazione delle richieste di interruzione, ma solo se lo stato di interruzione viene preservato.

Altri suggerimenti

L'unico modo di cui sono a conoscenza Thread.interrupt() è rotto è che in realtà non fa quello che sembra, ma può solo farlo effettivamente interrompere codice che lo ascolta.

Tuttavia, utilizzato correttamente, mi sembra un buon meccanismo integrato per la gestione e la cancellazione delle attività.

raccomando Concorrenza Java in pratica per ulteriori letture sull'uso corretto e sicuro dello stesso.

Il problema principale con Thread.interrupt() è che la maggior parte dei programmatori non conosce le trappole nascoste e lo usa nel modo sbagliato.Ad esempio, quando gestisci l'interruzione, ci sono metodi which chiaro la bandiera (quindi lo status viene perso).

Inoltre, la chiamata non interromperà sempre immediatamente il thread.Ad esempio, quando si blocca in qualche routine di sistema, non accadrà nulla.Infatti, se il thread non controlla il flag e non chiama mai un metodo Java che lancia un'eccezione InterruptException, quindi interromperlo non avrà alcun effetto.

No, non è buggato.In realtà è la base del modo in cui interrompi i thread in Java.È utilizzato nel framework Executor da java.util.concurrent - vedere l'implementazione di java.util.concurrent.FutureTask.Sync.innerCancel.

Per quanto riguarda il fallimento, non l'ho mai visto fallire e l'ho usato ampiamente.

Una ragione non menzionata è che il segnale di interruzione può andare perso, il che rende priva di significato la chiamata al metodo Thread.interrupt().Pertanto, a meno che il codice nel metodo Thread.run() non giri in un ciclo while, il risultato della chiamata a Thread.interrupt() è incerto.

L'ho notato quando ero in thread ONE Eseguo DriverManager.getConnection() quando non è disponibile alcuna connessione al database (ad esempio, il server è inattivo, quindi alla fine questa riga genera SQLException) e dal filo TWO Chiamo esplicitamente ONE.interrupt(), quindi entrambi ONE.interrupted() E ONE.isInterrupted() ritorno false anche se inserito come prima riga nel file catch{} bloccare dove SQLException viene gestito.

Ovviamente ho risolto questo problema implementando il semaforo aggiuntivo, ma è piuttosto problematico, poiché è il primo problema di questo tipo nei miei 15 anni di sviluppo Java.

Suppongo che sia a causa di un bug com.microsoft.sqlserver.jdbc.SQLServerDriver.E ho indagato di più per confermare che la chiamata al native la funzione consuma questa eccezione in tutti i casi in cui genera la propria, ma la preserva in caso di successo.

Tomek

PSHo trovato il questione analoga.

P.P.S Allego un brevissimo esempio di ciò che sto scrivendo sopra.La classe registrata può essere trovata in sqljdbc42.jar.Ho trovato questo bug nelle classi create il 20/08/2015, quindi ho aggiornato alla versione più recente disponibile (dal 12/01/2017) e il bug esiste ancora.

import java.sql.*;

public class TEST implements Runnable{
    static{
        try{
//register the proper driver
           Class.forName("com.microsoft.sqlserver.jdbc.SQLServerDriver");
        }
        catch(ClassNotFoundException e){
            System.err.println("Cannot load JDBC driver (not found in CLASSPATH).");
        }
    }

    public void run() {
        Thread.currentThread().interrupt();
        System.out.println(Thread.currentThread().isInterrupted());
//prints true
        try{
            Connection conn = DriverManager.getConnection("jdbc:sqlserver://xxxx\\xxxx;databaseName=xxxx;integratedSecurity=true");
        }
        catch (SQLException e){
            System.out.println(e.getMessage());
        }
        System.out.println(Thread.currentThread().isInterrupted());
//prints false
        System.exit(0);
    }

    public static void main(String[] args){
        (new Thread(new TEST())).start();
    }
}

Se passi qualcosa di completamente sbagliato, come "foo", al DriverManager.getConnection(), riceverai il messaggio "Nessun driver adatto trovato per foo", e la seconda stampa sarà ancora vera come ci si aspetterebbe.Ma se passi la stringa creata correttamente ma, ad esempio, il tuo server è inattivo o hai perso la connessione di rete (cosa che generalmente può verificarsi nell'ambiente di produzione), vedrai il messaggio java.net stampa dell'errore di timeout del socket e del thread interrupted() lo stato è PERSO.

Il problema non è che l'implementazione non sia difettosa, ma piuttosto che il thread si trova in uno stato sconosciuto quando viene interrotto e questo può portare a comportamenti imprevedibili.

Autorizzato sotto: CC-BY-SA insieme a attribuzione
Non affiliato a StackOverflow
scroll top