Manejo de la excepción interrumpida en Java
multithreading exception-handling (7)
¿Cuál es la diferencia entre las siguientes formas de manejar 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);
}
EDITAR: Me gustaría saber también en qué escenarios se utilizan estos dos.
¿Que estás tratando de hacer?
La InterruptedException
se lanza cuando un hilo está esperando o durmiendo y otro lo interrumpe usando el método de interrupt
en la clase Thread
. Entonces, si detecta esta excepción, significa que el hilo ha sido interrumpido. Generalmente no tiene sentido llamar Thread.currentThread().interrupt();
de nuevo, a menos que desee verificar el estado "interrumpido" del hilo desde otro lugar.
Con respecto a su otra opción de lanzar una excepción RuntimeException
, no parece una cosa muy acertada (¿quién detectará 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 tu lista de lanzamientos. Una Interrupción indica que otro hilo desea que su hilo termine. El motivo de esta solicitud no se hace evidente y es totalmente contextual, por lo tanto, 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 amistosa.
Java no lanzará aleatoriamente las 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 había desarrollado un gran conjunto de pruebas y usaba Thread.Sleep mucho. Ahora comenzamos a ejecutar las pruebas en nuestro servidor de CI y, a veces, debido a defectos en el código, se quedaban atascados en las 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 abortar la prueba no abortó el trabajo. Tuvimos que iniciar sesión en el cuadro y matar manualmente los procesos.
En pocas palabras, si simplemente lanza la InterruptedException coincide con la intención predeterminada de que su hilo debe terminar. Si no puede agregar InterruptedException a su lista de lanzamientos, lo envolvería en una RuntimeException.
Hay un argumento muy racional que se debe hacer para que InterruptedException sea una RuntimeException, ya que eso fomentaría un mejor manejo "predeterminado". No es una excepción RuntimeException solo porque los diseñadores se apegaron a una regla categórica de que una excepción RuntimeException debería representar un error en su código. Dado que una excepción interrumpida 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 bloqueo de bloqueo), y la Interrupción es otro método de subprocesos para tratar ese error.
Si sabes que hay que hacer una limpieza racional, hazlo. 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:
- Por defecto, añadir a tiros.
- Si no se le permite agregar a tiros, lance RuntimeException (e). (La mejor opción de múltiples opciones malas)
- Solo cuando conoce una causa explícita de la interrupción, manéjela como desee. Si su manejo es local a su método, entonces reinicie interrumpido por una llamada a Thread.currentThread (). Interrupt ().
Para mí, la clave de esto es: una excepción interrumpida no es nada que salga mal, es el hilo haciendo lo que le dijiste que hiciera. Por lo tanto, volver a verlo envuelto en una excepción RuntimeException no tiene sentido.
En muchos casos, tiene sentido volver a emitir una excepción envuelta en una excepción RuntimeException cuando dice: No sé qué fue lo que falló aquí y no puedo hacer nada para solucionarlo, solo quiero que salga del flujo de procesamiento actual y pulsa cualquier controlador de excepciones de toda la aplicación que tenga para que pueda registrarlo. Ese no es el caso de una InterruptedException, es solo el hilo que responde a la interrupt () llamada, está lanzando la InterruptedException para ayudar a cancelar el procesamiento del hilo de manera oportuna.
Por lo tanto, propague la InterruptedException, o cómala de manera inteligente (es decir, en un lugar donde habrá logrado lo que estaba destinado a hacer) y reinicie el indicador de interrupción. Tenga en cuenta que el indicador de interrupción se borra cuando se produce la excepción InterruptedException; la suposición de los desarrolladores de la biblioteca Jdk es que la captura de la excepción equivale a su manejo, por lo que, de forma predeterminada, la marca está desactivada.
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 realmente, e interrumpirlo equivale a un error.
Aquí hay una respuesta que escribí describiendo cómo funcionan las interrupciones, con un ejemplo . Puede ver en el código de ejemplo donde está utilizando la excepción InterruptedException para rescatar de un bucle while en el método de ejecución de Runnable.
Probablemente ha venido a hacer esta pregunta porque ha llamado a un método que lanza InterruptedException
.
En primer lugar, debería ver la throws InterruptedException
por lo que es: una parte de la firma del método y un posible resultado de llamar al método al que está llamando. Así que comience por aceptar el hecho de que una InterruptedException
es un resultado perfectamente válido de la llamada al método.
Ahora, si el método que está llamando arroja una excepción, ¿qué debería hacer su método? Puedes averiguar la respuesta pensando en lo siguiente:
¿Tiene sentido que el método que está implementando arroje una InterruptedException
? Dicho de otra manera, ¿es una InterruptedException
un resultado sensato al llamar a su método?
Si es así, entonces la excepción
throws InterruptedException
debe ser parte de la firma de su método, y debe dejar que la excepción se propague (es decir, no la atrape).Ejemplo : su método espera un valor de la red para finalizar el cálculo y devolver un resultado. Si la llamada de la red de bloqueo produce una
InterruptedException
su método no puede finalizar el cálculo de una manera normal.InterruptedException
propague laInterruptedException
.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 debe declarar su método con los
throws InterruptedException
y debe (¡debe!) Capturar la excepción. Ahora dos cosas son importantes a tener en cuenta en esta situación:Alguien interrumpió tu hilo. Ese alguien probablemente esté ansioso por cancelar la operación, terminar 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.
A pesar de que su método puede generar un valor de retorno razonable en caso de una
InterruptedException
el hecho de que se haya interrumpido el subproceso todavía puede ser importante. En particular, el código que llama a su método puede estar interesado en si se produjo una interrupción durante la ejecución de su método. Por lo tanto, debe registrar el hecho de que se produjo una interrupción estableciendo el indicador interrumpido:Thread.currentThread().interrupt()
Ejemplo : El usuario ha solicitado imprimir una suma de dos valores. Imprimir "
Failed to compute sum
" es aceptable si la suma no se puede calcular (y es mucho mejor que dejar que el programa se bloquee con un seguimiento de la pila debido a unaInterruptedException
). En otras palabras, no tiene sentido declarar este método con lathrows 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 ya debería estar claro que solo throw new RuntimeException(e)
es una mala idea. No es muy educado 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 de
Runnable
: Como puede haber descubierto, la firma deRunnable.run
no permite volver a emitirInterruptedExceptions
. Bueno, te registraste en la implementación deRunnable
, lo que significa que te registraste para lidiar con las posiblesInterruptedExceptions
. Elija una interfaz diferente, comoCallable
, o siga el segundo enfoque anterior.
Calling
Thread.sleep
: Estás intentando leer un archivo y la especificación dice que debes intentarlo 10 veces con 1 segundo en el medio. Usted llamaThread.sleep(1000)
. Por lo tanto, necesitas lidiar conInterruptedException
. Para un método comotryToReadFile
, tiene mucho sentido decir: "Si me interrumpen, no puedo completar mi acción de intentar leer el archivo" . En otras palabras, tiene mucho sentido para el método lanzarInterruptedExceptions
.
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 here .
Resulta que solo estaba leyendo sobre esto esta mañana en mi camino a trabajar en Java Concurrency In Practice de Brian Goetz. Básicamente dice que debes hacer una de dos cosas.
Propagar la
InterruptedException
: declare su método para lanzar laInterruptedException
comprobada para que la persona que llama tenga que lidiar con ella.Restaure la interrupción : a veces no puede lanzar
InterruptedException
. En estos casos, debe capturar laInterruptedException
y restaurar el estado de lainterrupt()
llamando al métodointerrupt()
en elcurrentThread
para que el código más arriba en la pila de llamadas pueda ver que se emitió una interrupción.
Solo quería agregar una última opción a lo que mencionan la mayoría de las personas y los artículos. Como mR_fr0g ha indicado, es importante manejar la interrupción correctamente, ya sea por:
Propagando la InterrupException
Restaurar estado de interrupción en el hilo
O además:
- Manejo personalizado de Interrupción.
No hay nada de malo en manejar la interrupción de manera personalizada dependiendo de sus circunstancias. Como una interrupción es una solicitud de finalización, a diferencia de un comando forzado, es perfectamente válido completar un trabajo adicional para permitir que la aplicación maneje la solicitud con gracia. Por ejemplo, si un subproceso está en espera, esperando IO o una respuesta de hardware, cuando recibe la interrupción, entonces es perfectamente válido cerrar las conexiones con gracia antes de terminar el subproceso.
Recomiendo comprender el tema, pero este artículo es una buena fuente de información: http://www.ibm.com/developerworks/java/library/j-jtp05236/
Yo diría que en algunos casos está bien no hacer nada. Probablemente no sea algo que deba hacer por defecto, pero en caso de que no haya forma de que se produzca la interrupción, no estoy seguro de qué hacer (es probable que se produzca un error de registro, pero eso no afecta el flujo del programa).
Un caso sería en caso de que tenga una cola de tareas (bloqueo). En caso de que tenga un subproceso de daemon que maneje estas tareas y no interrumpa el subproceso solo (a mi entender, el jvm no interrumpe los subprocesos del demonio en el cierre de jvm), no veo forma de que la interrupción ocurra, y por lo tanto podría ser simplemente ignorado (Sé que un hilo de demonio puede ser eliminado por el JVM en cualquier momento y, por lo tanto, es inadecuado en algunos casos).
EDITAR: Otro caso podría ser bloques protegidos, al menos según el tutorial de Oracle en: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html