Thread.stop e gli amici sono mai al sicuro in Java?

stop() , suspend() e resume() in java.lang.Thread sono deprecati perché non sicuri . La soluzione consigliata da Sun è quella di utilizzare Thread.interrupt() , ma tale approccio non funziona in tutti i casi. Ad esempio, se si chiama un metodo di libreria che non controlla esplicitamente o implicitamente il flag interrupted , non si ha altra scelta che attendere il completamento della chiamata.

Quindi, mi chiedo se sia ansible caratterizzare situazioni in cui è (sicuramente) sicuro chiamare stop() su una discussione. Ad esempio, sarebbe sicuro stop() un thread che non ha fatto altro che chiamare find(...) o match(...) su un java.util.regex.Matcher ?

(Se ci sono degli ingegneri Sun che leggono questo … una risposta definitiva sarebbe molto apprezzata).

EDIT : le risposte che semplicemente riaffermano il mantra che non dovresti chiamare stop() perché è deprecato, non sicuro, qualunque sia il punto mancante di questa domanda. So che è veramente pericoloso nella maggior parte dei casi e che, se esiste un’alternativa valida, dovresti sempre utilizzarla.

Questa domanda riguarda i casi di sottoinsieme in cui è sicuro. In particolare, che cos’è quel sottoinsieme?

Ecco il mio tentativo di rispondere alla mia domanda.

Penso che le seguenti condizioni dovrebbero essere sufficienti per fermare un singolo thread in modo sicuro usando Thread.stop() :

  1. L’esecuzione del thread non deve creare o mutare alcun stato (cioè oggetti Java, variabili di class, risorse esterne) che potrebbero essere visibili ad altri thread nel caso in cui il thread venga interrotto.
  2. L’esecuzione del thread non deve utilizzare notify alcun altro thread durante la sua normale esecuzione.
  3. Il thread non deve start o join altri thread, o interagire con quindi usando stop , suspend o resume .

(Il termine esecuzione del thread sopra copre tutto il codice a livello di applicazione e tutto il codice di libreria che viene eseguito dal thread.)

La prima condizione indica che un thread interrotto non lascerà strutture di dati o risorse esterne in uno stato incoerente. Ciò include le strutture di dati che potrebbe accedere (in lettura) all’interno di un mutex. La seconda condizione significa che un thread bloccabile non può lasciare altri thread in attesa. Ma proibisce anche l’uso di qualsiasi meccanismo di sincronizzazione diverso da quello degli oggetti semplici mutex.

Un thread stoppabile deve avere un modo per fornire i risultati di ogni calcolo al thread di controllo. Questi risultati vengono creati / mutati dal thread interrompibile, quindi è sufficiente accertarsi che non siano visibili dopo un arresto del thread. Ad esempio, i risultati potrebbero essere assegnati ai membri privati ​​dell’object Thread e “protetti” con una bandiera che è atomicamente dal thread per dire che è “fatto”.

EDIT : Queste condizioni sono piuttosto restrittive. Ad esempio, per un thread “valutatore di espressioni regolari” da arrestare in modo sicuro, se è necessario garantire che il motore regex non muti lo stato visibile esternamente. Il problema è che potrebbe fare, a seconda di come si implementa la discussione!

  1. I Pattern.compile(...) potrebbero aggiornare una cache statica di pattern compilati, e se lo facessero avrebbero (dovrebbero) usare un mutex per farlo. (In realtà, la versione di OpenJDK 6.0 non memorizza nella cache i Pattern, ma Sun potrebbe in teoria cambiare questo.)
  2. Se si tenta di evitare 1) compilando la regex nel thread di controllo e fornendo un Matcher pre-istanziato, il thread delle espressioni regolari si trasforma in uno stato visibile esternamente.

Nel primo caso, probabilmente saremmo nei guai. Ad esempio, supponiamo che sia stato utilizzato un HashMap per implementare la cache e che il thread sia stato interrotto mentre la HashMap era in fase di riorganizzazione.

Nel secondo caso, saremmo OK, a condizione che il Matcher non fosse passato a nessun altro thread e che il thread del controller non provasse a usare il Matcher dopo aver fermato il thread del regex matcher.

Quindi, dove ci lascia questo?

Bene, penso di aver identificato le condizioni in base alle quali i fili sono teoricamente sicuri di fermarsi. Penso anche che sia teoricamente ansible analizzare staticamente il codice di una discussione (e i metodi che chiama) per vedere se queste condizioni saranno sempre valide. Ma non sono sicuro che sia davvero pratico.

Ha senso ciò? Ho perso qualcosa?

MODIFICA 2

Le cose diventano un po ‘più pelose quando consideri che il codice che stiamo cercando di uccidere potrebbe essere non affidabile:

  1. Non possiamo contare su “promesse”; ad esempio annotazioni sul codice non attendibile che può essere killable o non killable.

  2. In realtà, dobbiamo essere in grado di impedire al codice non attendibile di fare cose che lo renderebbero impraticabile … in base ai criteri identificati.

Sospetto che ciò comporterebbe la modifica del comportamento di JVM (ad esempio, l’implementazione delle restrizioni di esecuzione quali thread sono autorizzati a bloccare o modificare) o un’implementazione completa di JSR di Isolates. Questo va oltre lo scopo di quello che stavo considerando come “fair game”.

Quindi lascia per ora la regola del caso codice non attendibile. O almeno, riconosci il fatto che il codice maligno può fare cose per renderlo non sicuro, e mettere da parte questo problema.

La mancanza di sicurezza viene dall’idea idea di sezioni critiche

 Take mutex do some work, temporarily while we work our state is inconsistent // all consistent now Release mutex 

Se si soffia via il filo e si è verificato che si trovi in ​​una sezione critica, l’object viene lasciato in uno stato incoerente, il che significa che non può essere utilizzato in modo sicuro da quel punto.

Affinché sia ​​sicuro di uccidere il thread è necessario comprendere l’intera elaborazione di ciò che viene fatto in quel thread, per sapere che non ci sono sezioni così critiche nel codice. Se stai utilizzando il codice della libreria, potresti non essere in grado di vedere la fonte e sapere che è sicura. Anche se è sicuro oggi potrebbe non essere domani.

(Molto artificioso) Esempio di ansible non sicurezza. Abbiamo una lista collegata, non è ciclica. Tutti gli algoritmi sono davvero veloci perché sappiamo che non è ciclico. Durante la nostra sezione critica introduciamo temporaneamente un ciclo. Poi veniamo spazzati via prima che emergiamo dalla sezione critica. Ora tutti gli algoritmi che usano il ciclo di lista per sempre. Nessun autore di librerie lo farebbe sicuramente! Come lo sai? Non puoi assumere che il codice che usi sia ben scritto.

Nell’esempio a cui si fa riferimento, è sicuramente ansible scrivere la funzionalità requreid in modo interrompibile. Più lavoro, ma è ansible essere al sicuro.

Prenderò un volantino: non ci sono sottoinsiemi documentati di oggetti e metodi che possono essere usati nei thread cancellabili, perché nessun autore di librerie vuole fare le garanzie.

Forse c’è qualcosa che non so, ma come ha detto java.sun.com , non è sicuro perché tutto ciò che questo thread sta gestendo rischia seriamente di essere danneggiato. Altri oggetti, connessioni, file aperti … per ovvi motivi, come “non chiudere la tua parola senza salvare prima”.

Per questo find(...) esemplificativo, non penso davvero che sarebbe una catastrofe semplicemente buttarlo via con un sutiless .stop()

Un esempio concreto potrebbe probabilmente aiutare qui. Se qualcuno può suggerire una buona alternativa al seguente uso di stop sarei molto interessato. La riscrittura di java.util.regex per supportare l’interruzione non viene conteggiata.

 import java.util.regex.*; import java.util.*; public class RegexInterruptTest { private static class BadRegexException extends RuntimeException { } final Thread mainThread = Thread.currentThread(); TimerTask interruptTask = new TimerTask() { public void run() { System.out.println("Stopping thread."); // Doesn't work: // mainThread.interrupt(); // Does work but is deprecated and nasty mainThread.stop(new BadRegexException()); } }; Timer interruptTimer = new Timer(true); interruptTimer.schedule(interruptTask, 2000L); String s = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaab"; String exp = "(a+a+){1,100}"; Pattern p = Pattern.compile(exp); Matcher m = p.matcher(s); try { System.out.println("Match: " + m.matches()); interruptTimer.cancel(); } catch(BadRegexException bre) { System.out.println("Oooops"); } finally { System.out.println("All over"); } } } 

Ci sono modi per utilizzare Thread.stop () relativamente stabile senza perdite di memoria o descrittori di file (gli FD sono eccezionalmente vulnerabili su * NIX) ma si deve fare affidamento su di esso solo se si è costretti a gestire codice di terze parti. Non usarlo mai per ottenere il risultato se puoi avere il controllo sul codice stesso.

Se uso Thread.stop insieme a w / interrupt () e alcuni altri hack come aggiungere gestori di registrazione personalizzati per lanciare il ThreadDeath intrappolato, aggiungendo unhandleExceltionHandler, eseguendo il proprio ThreadGroup (sincronizzandosi su ’em), ecc …

Ma questo merita un argomento completamente nuovo.

Ma in questo caso sono i designer Java che ti dicono; e sono più autorevoli nella loro lingua, quindi entrambi noi 🙂

Solo una nota: alcuni di loro sono piuttosto incapaci

Se la mia comprensione è corretta, il problema ha a che fare con i blocchi di sincronizzazione che non vengono rilasciati poiché il ThreadInterruptedException () generato si propaga sullo stack.

Detto per scontato, è intrinsecamente non sicuro perché non si può mai sapere se una “chiamata di metodo interiore” in cui ci si è imbattuti al momento stesso () è stata invocata ed eseguita, era effettivamente in grado di bloccare la sincronizzazione, e quindi cosa gli ingegneri di Java dicono che è, apparentemente, inequivocabilmente giusto.

Quello che personalmente non capisco è il motivo per cui dovrebbe essere imansible rilasciare qualsiasi blocco di sincronizzazione poiché questo particolare tipo di Eccezione si propaga nello stack, passando quindi tutti i delimitatori di metodo / sincronizzazione di blocco “}, che causano il rilascio di qualsiasi blocco per qualsiasi altro tipo di eccezione.

Ho un server scritto in java e se l’amministratore di quel servizio vuole un “cold shutdown”, allora è semplicemente NECESSARIO poter interrompere tutte le attività in esecuzione, non importa cosa. La coerenza dello stato di qualsiasi object non è una preoccupazione perché tutto quello che sto cercando di fare è uscire. Il più velocemente ansible.

Non esiste un modo sicuro per uccidere un thread.

Né esiste un sottoinsieme di situazioni in cui è sicuro. Anche se funziona al 100% durante i test su Windows, potrebbe danneggiare la memoria del processo JVM in Solaris o filtrare le risorse del thread sotto Linux.

Si dovrebbe sempre ricordare che sotto il thread Java c’è un thread reale, nativo e non sicuro.

Quel thread nativo funziona con strutture di controllo e dati nativi di basso livello. Ucciderlo può lasciare quelle strutture di dati nativi in ​​uno stato non valido, senza un modo per recuperare.

Non è ansible per la macchina Java prendere in considerazione tutte le possibili conseguenze, poiché il thread può allocare / utilizzare risorse non solo all’interno del processo JVM, ma anche all’interno del kernel del sistema operativo.

In altre parole, se la libreria di thread nativi non fornisce un metodo sicuro per uccidere () un thread, Java non può fornire alcuna garanzia migliore di quella. E tutte le implementazioni native a me note affermano che uccidere thread è un’attività pericolosa.

Tutte le forms di controllo della concorrenza possono essere fornite dalle primitive di sincronizzazione Java mediante la costruzione di controlli di concorrenza più complessi adatti al problema.

I motivi della deprecazione sono chiaramente indicati nel link che fornisci. Se sei disposto ad accettare i motivi per cui, allora sentiti libero di usare quelle funzionalità.

Tuttavia, se si sceglie di utilizzare tali funzioni, si accetta anche che il supporto per tali funzionalità possa interrompersi in qualsiasi momento.

Modifica: ripeterò il motivo della deprecazione e anche come evitarli.

Dal momento che l’unico pericolo è che gli oggetti a cui è ansible fare riferimento dal thread stop edule potrebbero essere corrotti, è sufficiente clone la String prima di passarla alla Thread . Se non esistono oggetti condivisi, la minaccia di oggetti danneggiati nel programma al di fuori del Thread stop non è più lì.