¿Thread.stop y sus amigos están a salvo en Java?

stop() , suspend() y resume() en java.lang.Thread están en desuso porque no son seguros . El trabajo recomendado por Sun es usar Thread.interrupt() , pero ese enfoque no funciona en todos los casos. Por ejemplo, si llama a un método de biblioteca que no verifica explícita o implícitamente el indicador interrupted , no tiene más remedio que esperar a que finalice la llamada.

Entonces, me pregunto si es posible caracterizar situaciones en las que es (probablemente) seguro llamar a stop() en un subproceso. Por ejemplo, ¿sería seguro stop() un hilo que no hizo más que llamar a find(...) o match(...) en un java.util.regex.Matcher ?

(Si hay ingenieros de Sun leyendo esto … una respuesta definitiva sería muy apreciada).

EDITAR : respuestas que simplemente repiten el mantra que no debe llamar a stop() porque está obsoleto, inseguro, lo que sea que le falte el sentido de esta pregunta. Sé que es genuinamente inseguro en la mayoría de los casos, y que si hay una alternativa viable, siempre debes usarla.

Esta pregunta es sobre los casos de subconjuntos donde es seguro. Específicamente, ¿qué es ese subconjunto?

Aquí está mi bash de responder mi propia pregunta.

Creo que las siguientes condiciones deberían ser suficientes para detener un hilo de manera segura usando Thread.stop() :

  1. La ejecución del subproceso no debe crear ni modificar ningún estado (es decir, objetos Java, variables de clase, recursos externos) que puedan ser visibles para otros subprocesos en el caso de que se detenga el subproceso.
  2. La ejecución del subproceso no debe usar notify a ningún otro subproceso durante su ejecución normal.
  3. El hilo no debe start o join otros hilos, o interactuar con el uso de stop , suspend o resume .

(El término ejecución de subproceso anterior cubre todos los códigos de nivel de aplicación y todo el código de biblioteca que ejecuta el subproceso).

La primera condición significa que un hilo detenido no dejará ninguna estructura de datos o recursos externos en un estado incoherente. Esto incluye estructuras de datos a las que podría estar accediendo (leyendo) dentro de un mutex. La segunda condición significa que un hilo que se puede detener no puede dejar esperando a otro hilo. Pero también prohíbe el uso de cualquier mecanismo de sincronización distinto de los mutex de objeto simple.

Un hilo que se puede detener debe tener una forma de entregar los resultados de cada cálculo al hilo de control. Estos resultados son creados / mutilados por el hilo que se puede detener, por lo que simplemente debemos asegurarnos de que no sean visibles después de detener un hilo. Por ejemplo, los resultados se podrían asignar a miembros privados del objeto Thread y “guardado” con un indicador que es atómicamente por el hilo para decir que está “hecho”.

EDITAR : Estas condiciones son bastante restrictivas. Por ejemplo, para que un hilo “evaluador de expresiones regulares” se detenga con seguridad, si debemos garantizar que el motor de expresiones regulares no mutee ningún estado externo visible. ¡El problema es que podría funcionar, dependiendo de cómo implemente el hilo!

  1. Los Pattern.compile(...) podrían actualizar un caché estático de patrones comstackdos, y si lo hicieran, (deberían) usar un mutex para hacerlo. (En realidad, la versión OpenJDK 6.0 no almacena patrones en caché, pero Sun podría posiblemente cambiar esto).
  2. Si intenta evitar 1) comstackndo la expresión regular en el hilo de control y suministrando un Matcher previamente instanciado, entonces el hilo de expresión regular cambia el estado externo visible.

En el primer caso, probablemente estaríamos en problemas. Por ejemplo, supongamos que se utilizó un HashMap para implementar el caché y que el hilo se interrumpió mientras el HashMap se estaba reorganizando.

En el segundo caso, estaríamos bien siempre que el Matcher no haya sido pasado a otro hilo, y siempre que el hilo del controlador no trate de usar el Matcher después de detener el hilo del matger regex.

¿A dónde nos lleva esto?

Bueno, creo que he identificado las condiciones bajo las cuales los hilos son teóricamente seguros para detenerse. También creo que es teóricamente posible analizar de forma estática el código de un hilo (y los métodos que llama) para ver si estas condiciones siempre se mantendrán. Pero, no estoy seguro de si esto es realmente práctico.

¿Esto tiene sentido? ¿Me he perdido algo?

EDIT 2

Las cosas se ponen un poco más peludas cuando consideras que el código que podríamos estar tratando de matar podría no ser de confianza:

  1. No podemos confiar en las “promesas”; por ejemplo, anotaciones sobre el código que no es de confianza, ya sea que se puede cancelar o no.

  2. De hecho, debemos poder evitar que el código que no es de confianza haga cosas que lo hagan inaccesible … según los criterios identificados.

Sospecho que esto implicaría modificar el comportamiento de JVM (por ejemplo, implementando restricciones de tiempo de ejecución que los hilos pueden bloquear o modificar), o una implementación completa de Aislados JSR. Eso está más allá del scope de lo que estaba considerando como “juego limpio”.

Así que vamos a descartar el caso de código no confiable por el momento. O al menos, reconozca que el código malicioso puede hacer cosas para que no se eliminen de manera segura, y deje de lado ese problema.

La falta de seguridad proviene de la idea idea de secciones críticas

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

Si soplas el hilo y ocurre que está en una sección crítica, el objeto queda en un estado incoherente, lo que significa que no se puede usar de forma segura desde ese punto.

Para que sea seguro matar el hilo, necesita comprender todo el procesamiento de lo que se está haciendo en ese hilo, para saber que no hay tales secciones críticas en el código. Si está utilizando el código de la biblioteca, es posible que no pueda ver la fuente y sepa que es seguro. Incluso si hoy es seguro, puede que no sea mañana.

(Muy artificial) Ejemplo de posible inseguridad. Tenemos una lista vinculada, no es cíclica. Todos los algoritmos son muy rápidos porque sabemos que no son cíclicos. Durante nuestra sección crítica, introducimos temporalmente un ciclo. Luego nos quedamos impresionados antes de salir de la sección crítica. Ahora todos los algoritmos usan la lista de bucle para siempre. ¡Ningún autor de la biblioteca haría eso seguramente! ¿Cómo lo sabes? No puede suponer que el código que usa está bien escrito.

En el ejemplo que señala, seguramente es posible escribir la funcionalidad requreid de forma interrumpible. Más trabajo, pero posible para estar seguro.

Tomaré un folleto: no hay un subconjunto documentado de Objetos y métodos que puedan usarse en hilos cancelables, porque ningún autor de la biblioteca desea hacer las garantías.

Tal vez haya algo que no sé, pero como dijo java.sun.com , no es seguro porque cualquier cosa que esté manejando este hilo está en grave riesgo de dañarse. Otros objetos, conexiones, archivos abiertos … por razones obvias, como “no cierres tu Word sin guardar primero”.

Para este find(...) ejemplo, realmente no creo que sería una catástrofe simplemente patearlo con un sutiless .stop()

Un ejemplo concreto probablemente sea útil aquí. Si alguien puede sugerir una buena alternativa para el siguiente uso de la parada, estaría muy interesado. Volver a escribir java.util.regex para admitir la interrupción no cuenta.

 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"); } } } 

Hay formas de usar Thread.stop () relativamente estable sin memory leaks o descripciones de archivos (los FD son excepcionalmente propensos a fugas en * NIX) pero solo deberá confiar en ellos si se ve obligado a administrar un código de terceros. Nunca lo use para lograr el resultado si puede tener control sobre el código en sí.

Si utilizo Thread.stop junto con w / interrupt () y algo más, hackeo cosas como agregar manejadores de registro personalizados para volver a lanzar ThreadDeath atrapado, agregar unhandleExceltionHandler, ejecutar en su propio ThreadGroup (sync over ’em), etc …

Pero eso merece un tema completamente nuevo.

Pero en este caso son los diseñadores de Java los que le dicen; y son más autócratas en su idioma que cualquiera de nosotros 🙂

Solo una nota: bastantes de ellos son bastante desorientados

Si mi comprensión es correcta, el problema tiene que ver con lockings de sincronización que no se liberan, ya que la ThreadInterruptedException () generada propaga la stack.

Dando esto por sentado, es intrínsecamente inseguro porque nunca se puede saber si alguna “llamada al método interno” en el momento en que se invoca y se ejecuta stop (), efectivamente estaba manteniendo algún locking de sincronización, y entonces Los ingenieros de Java dicen que es, aparentemente, inequívocamente correcto.

Lo que personalmente no entiendo es por qué debería ser imposible liberar cualquier locking de sincronización ya que este tipo particular de Excepción se propaga por la stack, pasando todos los delimitadores de bloque de método / sincronización ‘}’, que hacen que se liberen los lockings para cualquier otro tipo de excepción.

Tengo un servidor escrito en Java, y si el administrador de ese servicio quiere un “cierre en frío”, entonces es simplemente NECESARIO poder detener toda la actividad en ejecución sin importar qué. La consistencia del estado de cualquier objeto no es una preocupación porque todo lo que bash hacer es SALIR. Tan rápido como pueda.

No hay una manera segura de matar un hilo.

Tampoco hay un subconjunto de situaciones donde sea seguro. Incluso si funciona al 100% mientras se prueba en Windows, puede dañar la memoria de proceso de JVM en Solaris o los recursos de la fuga de subprocesos en Linux.

Uno siempre debe recordar que debajo del hilo de Java hay un hilo real, inseguro y nativo.

Ese hilo nativo funciona con estructuras nativas, de bajo nivel, de datos y control. Matarlo puede dejar esas estructuras de datos nativas en un estado inválido, sin una forma de recuperación.

No hay forma de que la máquina Java tenga en cuenta todas las consecuencias posibles, ya que la secuencia puede asignar / usar recursos no solo dentro del proceso de JVM, sino también dentro del núcleo del sistema operativo.

En otras palabras, si la biblioteca de hilos nativos no proporciona una forma segura de matar () un hilo, Java no puede proporcionar ninguna garantía mejor que eso. Y todas las implementaciones nativas conocidas me dicen que matar el hilo es un asunto peligroso.