Manejo de excepción interrumpida en Java
¿ Cuál es la diferencia entre las siguientes formas de manipulación InterruptedException
? ¿Cuál es la mejor manera de hacerlo?
try {
// ...
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
...o:
try {
//...
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
También me gustaría saber en qué escenarios se utilizan estos dos.
¿Cuál es la diferencia entre las siguientes formas de manejar InterruptedException? ¿Cuál es la mejor manera de hacerlo?
Probablemente hayas llegado a hacer esta pregunta porque has llamado a un método que arroja InterruptedException
.
En primer lugar, deberías ver throws InterruptedException
qué es: una parte de la firma del método y un posible resultado de llamar al método que estás llamando. Así que comience por aceptar el hecho de que an InterruptedException
es un resultado perfectamente válido de la llamada al método.
Ahora, si el método al que estás llamando arroja dicha excepción, ¿qué debería hacer tu método? Puedes encontrar la respuesta pensando en lo siguiente:
¿Tiene sentido que el método que está implementando arroje un InterruptedException
? Dicho de otra manera, ¿es InterruptedException
un resultado sensato al llamar a su método?
En caso afirmativo , entonces
throws InterruptedException
debería ser parte de la firma de su método y debería dejar que la excepción se propague (es decir, no detectarla en absoluto).Ejemplo : su método espera un valor de la red para finalizar el cálculo y devolver un resultado. Si la llamada de red de bloqueo genera un
InterruptedException
método, su método no puede finalizar el cálculo de forma normal. Dejas que seInterruptedException
propague.int computeSum(Server server) throws InterruptedException { // Any InterruptedException thrown below is propagated int a = server.getValueA(); int b = server.getValueB(); return a + b; }
Si no , entonces no deberías declarar tu método con
throws InterruptedException
y deberías (¡debes!) detectar la excepción. Ahora es importante tener en cuenta dos cosas en esta situación:Alguien interrumpió tu hilo. Probablemente alguien esté ansioso por cancelar la operación, finalizar el programa con gracia o lo que sea. Debes ser cortés con esa persona y regresar de tu método sin más preámbulos.
Aunque su método puede lograr producir un valor de retorno razonable en caso de
InterruptedException
que el hilo haya sido interrumpido, aún puede ser importante. En particular, el código que llama a su método puede estar interesado en saber si se produjo una interrupción durante la ejecución de su método. Por lo tanto, debes registrar el hecho de que se produjo una interrupción configurando el indicador de interrupción:Thread.currentThread().interrupt()
Ejemplo : El usuario ha solicitado imprimir una suma de dos valores. Imprimir "
Failed to compute sum
" es aceptable si no se puede calcular la suma (y mucho mejor que dejar que el programa falle con un seguimiento de la pila debido a unInterruptedException
). En otras palabras, no tiene sentido declarar este método conthrows 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"); } }
A estas alturas debería quedar claro que simplemente hacerlo throw new RuntimeException(e)
es una mala idea. No es muy cortés con la persona que llama. Podría inventar una nueva excepción de tiempo de ejecución, pero la causa raíz (alguien quiere que el hilo detenga la ejecución) podría perderse.
Otros ejemplos:
Implementación
Runnable
: Como habrás descubierto, la firma deRunnable.run
no permite volver a lanzarloInterruptedExceptions
. Bueno, te registraste para implementarRunnable
, lo que significa que te registraste para lidiar con posiblesInterruptedExceptions
. Elija una interfaz diferente, comoCallable
, o siga el segundo enfoque anterior.
Llamada
Thread.sleep
: estás intentando leer un archivo y la especificación dice que debes intentarlo 10 veces con 1 segundo entre ellas. LlamaThread.sleep(1000)
. Entonces, debes lidiar conInterruptedException
. Para un método comotryToReadFile
este, tiene mucho sentido decir: "Si me interrumpen, no puedo completar mi acción de intentar leer el archivo" . En otras palabras, tiene mucho sentido que el método arrojeInterruptedExceptions
.
String tryToReadFile(File f) throws InterruptedException { for (int i = 0; i < 10; i++) { if (f.exists()) return readFile(f); Thread.sleep(1000); } return null; }
Esta publicación ha sido reescrita como un artículo aquí .
Da la casualidad de que estaba leyendo sobre esto esta mañana de camino al trabajo en Java Concurrency In Practice de Brian Goetz. Básicamente dice que debes hacer una de tres cosas.
Propagar el
InterruptedException
- Declare su método para lanzar el marcadoInterruptedException
para que la persona que llama tenga que lidiar con él.Restaurar la interrupción : a veces no se puede lanzar
InterruptedException
. En estos casos, debe detectarInterruptedException
y restaurar el estado de la interrupción llamando alinterrupt()
métodocurrentThread
para que el código que se encuentra más arriba en la pila de llamadas pueda ver que se emitió una interrupción y regresar rápidamente del método. Nota: esto sólo es aplicable cuando su método tiene semántica de "intentar" o "mejor esfuerzo", es decir, no sucedería nada crítico si el método no logra su objetivo. Por ejemplo,log()
osendMetric()
puede ser tal método, oboolean tryTransferMoney()
, pero novoid transferMoney()
. Consulte aquí para obtener más detalles.- Ignore la interrupción dentro del método, pero restaure el estado al salir , por ejemplo, a través de Guava
Uninterruptibles
.Uninterruptibles
asumir el código repetitivo como en el ejemplo de Tarea no cancelable en JCIP § 7.1.3.
¿Que estás tratando de hacer?
Se InterruptedException
lanza cuando un hilo está esperando o inactivo y otro hilo lo interrumpe usando el interrupt
método de la clase Thread
. Entonces, si detecta esta excepción, significa que el hilo ha sido interrumpido. Por lo general, no tiene sentido Thread.currentThread().interrupt();
volver a llamar, a menos que desee verificar el estado "interrumpido" del hilo desde otro lugar.
Con respecto a su otra opción de lanzar un RuntimeException
, no parece muy inteligente (¿quién se dará cuenta de esto? ¿Cómo se manejará?), pero es difícil decir más sin información adicional.
La opción predeterminada correcta es agregar InterruptedException a su lista de lanzamientos. Una interrupción indica que otro hilo desea que el suyo termine. El motivo de esta solicitud no es evidente y es completamente contextual, por lo que si no tiene ningún conocimiento adicional debe asumir que es solo un cierre amistoso, y cualquier cosa que evite ese cierre es una respuesta no amigable.
Java no arrojará aleatoriamente InterruptedException, todos los consejos no afectarán su aplicación, pero me he topado con un caso en el que el desarrollador que siguió la estrategia de "tragar" se volvió muy inconveniente. Un equipo desarrolló un gran conjunto de pruebas y utilizó mucho Thread.Sleep. Ahora comenzamos a ejecutar las pruebas en nuestro servidor CI y, a veces, debido a defectos en el código, nos quedábamos atascados en esperas permanentes. Para empeorar la situación, al intentar cancelar el trabajo de CI, nunca se cerró porque el Thread.Interrupt que estaba destinado a cancelar la prueba no canceló el trabajo. Tuvimos que iniciar sesión en el cuadro y finalizar manualmente los procesos.
En pocas palabras, si simplemente lanza la InterruptedException, estará coincidiendo con la intención predeterminada de que su hilo debería finalizar. Si no puede agregar InterruptedException a su lista de lanzamientos, lo incluiría en una RuntimeException.
Se puede argumentar muy racionalmente que InterruptedException debería ser una RuntimeException en sí misma, ya que eso fomentaría un mejor manejo "predeterminado". No es una RuntimeException solo porque los diseñadores se apegaron a una regla categórica de que una RuntimeException debería representar un error en su código. Dado que una InterruptedException no surge directamente de un error en su código, no lo es. Pero la realidad es que a menudo surge una InterruptedException porque hay un error en su código (es decir, un bucle sin fin, un punto muerto), y la Interrupción es el método de algún otro hilo para tratar ese error.
Si sabe que es necesario realizar una limpieza racional, hágalo. Si conoce una causa más profunda de la interrupción, puede asumir un manejo más completo.
Entonces, en resumen, sus opciones de manejo deben seguir esta lista:
- De forma predeterminada, agregue a los lanzamientos.
- Si no se le permite agregar lanzamientos, lanza RuntimeException(e). (La mejor opción entre múltiples opciones malas)
- Sólo cuando conozca una causa explícita de la interrupción, maneje como desee. Si su manejo es local para su método, reinicie interrumpido por una llamada a Thread.currentThread().interrupt().
Para mí, la clave de esto es: una InterruptedException no significa que nada vaya mal, es el hilo que hace lo que usted le dijo que hiciera. Por lo tanto, volver a lanzarlo envuelto en una RuntimeException no tiene ningún sentido.
En muchos casos, tiene sentido volver a generar una excepción envuelta en una RuntimeException cuando dices: No sé qué salió mal aquí y no puedo hacer nada para solucionarlo, solo quiero que salga del flujo de procesamiento actual. y presione cualquier controlador de excepciones de toda la aplicación que tenga para que pueda registrarlo. Ese no es el caso con una InterruptedException, es solo el subproceso que responde a la llamada de interrupción (), y lanza la InterruptedException para ayudar a cancelar el procesamiento del subproceso de manera oportuna.
Así que propague la InterruptedException, o cómela inteligentemente (es decir, en un lugar donde habrá logrado lo que debía hacer) y restablezca el indicador de interrupción. Tenga en cuenta que el indicador de interrupción se borra cuando se lanza la InterruptedException; la suposición que hacen los desarrolladores de la biblioteca Jdk es que detectar la excepción equivale a manejarla, por lo que, de forma predeterminada, la bandera está borrada.
Definitivamente, la primera forma es mejor, el segundo ejemplo publicado en la pregunta no es útil a menos que no espere que el hilo se interrumpa, e interrumpirlo equivale a un error.
Aquí hay una respuesta que escribí que describe cómo funcionan las interrupciones, con un ejemplo . Puede ver en el código de ejemplo dónde se utiliza InterruptedException para salir de un bucle while en el método de ejecución de Runnable.