Manejo de excepción interrumpida en Java

Respuestas:

436

¿Cuál es la diferencia entre las siguientes formas de manejar la InterruptedException? ¿Cuál es la mejor manera de hacerlo?

Probablemente has venido a hacer esta pregunta porque has llamado a un método que arroja InterruptedException.

En primer lugar, debe ver throws InterruptedExceptionqué es: una parte de la firma del método y un posible resultado de llamar al método que está llamando. Comience por aceptar el hecho de que an InterruptedExceptiones un resultado perfectamente válido de la llamada al método.

Ahora, si el método que está llamando arroja tal excepción, ¿qué debe hacer su método? Puede encontrar la respuesta pensando en lo siguiente:

¿Tiene sentido para el método que está implementando para lanzar un InterruptedException? Dicho de otra manera, ¿es un InterruptedExceptionresultado razonable al llamar a su método?

  • En caso afirmativo , throws InterruptedExceptiondebería ser parte de la firma de su método, y debería permitir que la excepción se propague (es decir, no la capte 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 arroja un, InterruptedExceptionsu método no puede finalizar el cálculo de una manera normal. Dejaste la InterruptedExceptionpropagación.

    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 throws InterruptedExceptiony debe (¡debe!) Atrapar la excepción. Ahora, dos cosas son importantes a tener en cuenta en esta situación:

    1. Alguien interrumpió tu hilo. Que alguien probablemente esté ansioso por cancelar la operación, finalizar el programa con gracia o lo que sea. Debe ser cortés con esa persona y regresar de su método sin más preámbulos.

    2. Aunque su método puede lograr producir un valor de retorno razonable en caso de InterruptedExceptionque el hilo se haya 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, debe registrar el hecho de que se produjo una interrupción configurando el indicador interrumpido:Thread.currentThread().interrupt()

    Ejemplo : el usuario ha pedido imprimir una suma de dos valores. La impresión " Failed to compute sum" es aceptable si no se puede calcular la suma (y mucho mejor que dejar que el programa se bloquee con un seguimiento de pila debido a un InterruptedException). En otras palabras, no tiene sentido declarar este método con throws 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 estar claro que solo hacer throw new RuntimeException(e)es una mala idea. No es muy amable 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ónRunnable : Como puede haber descubierto, la firma de Runnable.runno permite volver a lanzar InterruptedExceptions. Bueno, te registraste en la implementación Runnable, lo que significa que te registraste para hacer frente a posibles InterruptedExceptions. Elija una interfaz diferente, como Callable, o siga el segundo enfoque anterior.

 

LlamadasThread.sleep : está intentando leer un archivo y la especificación dice que debe intentarlo 10 veces con 1 segundo en el medio. Tu llamas Thread.sleep(1000). Entonces, tienes que lidiar con eso InterruptedException. Para un método como el tryToReadFileque 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 arroje InterruptedExceptions.

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í .

aioobe
fuente
¿Cuál es el problema con el segundo método?
blitzkriegz
44
El artículo dice que debe llamar interrupt()para preservar el estado interrumpido. ¿Cuál es la razón detrás de no hacer esto en un Thread.sleep()?
oksayt
77
No estoy de acuerdo, en el caso de que su hilo no admita interrupciones, cualquier interrupción que se reciba indica una condición inesperada (es decir, un error de programación, mal uso de la API), y rescatar con una RuntimeException es una respuesta apropiada (falla rápida ) A menos que se considere parte del contrato general de un hilo que, incluso si no admite interrupciones, debe continuar la operación cuando las reciba.
amee
12
Llamar a métodos que arrojan InterruptedExceptions y dicen que "no admite interrupciones" parece una programación descuidada y un error esperando a suceder. Dicho de otra manera; Si se llama a un método que se declara a tirar InterruptedException debería no ser una condición inesperada si ese método en el hecho de tirar de esas excepciones.
aioobe
1
@MartiNito, no, llamar Thread.currentThread.interrupt()a un InterruptedExceptionbloque catch solo establecerá la bandera de interrupción. No hará que otro InterruptedExceptionsea ​​arrojado / atrapado en un InterruptedExceptionbloque de captura externo .
aioobe
98

Da la casualidad de que estaba leyendo sobre esto esta mañana en mi camino al trabajo en Java Concurrency In Practice de Brian Goetz. Básicamente él dice que deberías hacer una de tres cosas

  1. Propague elInterruptedException : declare su método para lanzar el marcado InterruptedExceptionpara que su interlocutor tenga que lidiar con él.

  2. Restaurar la interrupción : a veces no puedes lanzar InterruptedException. En estos casos, debe capturar InterruptedExceptiony restaurar el estado de interrupción llamando al interrupt()método en el currentThreadpara que el código 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 solo es aplicable cuando su método tiene una semántica de "intento" o "mejor esfuerzo", es decir, nada crítico sucedería si el método no logra su objetivo. Por ejemplo, log()o sendMetric()puede ser tal método, o boolean tryTransferMoney(), pero no void transferMoney(). Ver aquí para más detalles.

  3. Ignore la interrupción dentro del método, pero restaure el estado al salir , por ejemplo, a través de Guava's Uninterruptibles. Uninterruptiblestomar el código repetitivo como en el ejemplo de Tarea no cancelable en JCIP § 7.1.3.
mR_fr0g
fuente
10
"A veces no puedes lanzar InterruptedException" - Yo diría, a veces no es apropiado que el método propague InterruptedExceptions. Su formulación parece sugerir que debería volver a lanzar la InterruptedException siempre que pueda .
aioobe
1
Y si llegaste a leer el capítulo 7, verás que hay algunas sutilezas en esto, como las del Listado 7.7, donde una tarea no cancelable no restablece la interrupción de inmediato , sino solo después de que se hace. También Goetz tiene muchos coautores de ese libro ...
Fizz
1
Me gusta su respuesta porque es concisa, sin embargo, creo que también debería indicar que debe regresar suave y rápidamente de su función en el segundo caso. Imagine un ciclo largo (o infinito) con un Thread.sleep () en el medio. La captura de la InterruptedException debería llamar a Thread.currentThread (). Interrupt () y salir del bucle.
Yann Vo
@ YannVo He editado la respuesta para aplicar su sugerencia.
leventov
19

¿Que estás tratando de hacer?

Se InterruptedExceptionemite cuando un hilo está esperando o durmiendo y otro hilo lo interrumpe usando el interruptmétodo en 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 una cosa muy sabia (¿quién atrapará esto? ¿Cómo se manejará?) Pero es difícil decir más sin información adicional.

Grodriguez
fuente
14
Llamar Thread.currentThread().interrupt()establece el indicador de interrupción (nuevamente), lo cual es útil de hecho si queremos asegurarnos de que la interrupción se note y se procese en un nivel superior.
Péter Török
1
@ Péter: Estaba actualizando la respuesta para mencionar esto. Gracias.
Grodriguez
12

Para mí, la clave de esto es: una InterruptedException no es que algo salga mal, es el hilo que hace lo que le dijiste que hiciera. Por lo tanto, volver a lanzarlo envuelto en una RuntimeException no tiene sentido.

En muchos casos, tiene sentido volver a lanzar una excepción envuelta en una RuntimeException cuando dice: 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 hilo que responde a haber llamado a interrupt (), lanza la InterruptedException para ayudar a cancelar el procesamiento del hilo de manera oportuna.

Entonces, propague la InterruptedException, o cómela de manera inteligente (es decir, en un lugar donde habrá logrado lo que estaba destinado a hacer) y restablezca la bandera de interrupción. Tenga en cuenta que el indicador de interrupción se borra cuando se lanza la InterruptedException; la suposición de los desarrolladores de la biblioteca Jdk es que capturar la excepción equivale a manejarla, por lo que, de forma predeterminada, se borra la bandera.

Entonces, 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 dónde está usando la InterruptedException para rescatar un ciclo while en el método de ejecución de Runnable.

Nathan Hughes
fuente
10

La opción predeterminada correcta es agregar InterruptedException a su 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 completamente contextual, por lo que si no tiene ningún conocimiento adicional, debe asumir que es solo un cierre amigable, y cualquier cosa que evite ese cierre es una respuesta no amigable.

Java no lanzará aleatoriamente InterruptedException, todos los consejos no afectarán su aplicación, pero me he encontrado con un caso en el que los desarrolladores que siguen la estrategia de "tragar" se volvieron muy inconvenientes. Un equipo desarrolló un gran conjunto de pruebas y usó Thread.Sleep mucho. Ahora comenzamos a ejecutar las pruebas en nuestro servidor de CI y, a veces, debido a defectos en el código, quedamos atrapados en esperas permanentes. Para empeorar la situación, al intentar cancelar el trabajo de CI nunca se cerró debido a que Thread.Interrupt que estaba destinado a abortar la prueba no abortó el trabajo. Tuvimos que iniciar sesión en el cuadro y eliminar manualmente los procesos.

En resumen, si simplemente lanza la InterruptedException, está haciendo coincidir la intención predeterminada de que su hilo debería terminar. Si no puede agregar InterruptedException a su lista de lanzamiento, lo envolvería en RuntimeException.

Hay un argumento muy racional para hacer 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 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 punto muerto), y la Interrupción es el método de otro hilo para tratar ese error.

Si sabe que hay que realizar una limpieza racional, hágalo. Si conoce una causa más profunda para la interrupción, puede asumir un manejo más integral.

En resumen, sus opciones de manejo deben seguir esta lista:

  1. Por defecto, agregar a los lanzamientos.
  2. Si no se permite agregar a los lanzamientos, lanza RuntimeException (e). (La mejor opción de múltiples opciones malas)
  3. Solo 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 ().
nedruod
fuente
3

Solo quería agregar una última opción a lo que la mayoría de las personas y artículos mencionan. Como ha declarado mR_fr0g, es importante manejar la interrupción correctamente ya sea:

  • Propagando la excepción de interrupción

  • Restaurar estado de interrupción en subproceso

O adicionalmente:

  • Manejo personalizado de interrupción

No hay nada de malo en manejar la interrupción de forma personalizada según sus circunstancias. Como una interrupción es una solicitud de terminación, a diferencia de un comando enérgico, 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 una respuesta de E / S o de hardware, cuando recibe la interrupción, entonces es perfectamente válido cerrar con gracia cualquier conexión antes de terminar el subproceso.

Recomiendo encarecidamente comprender el tema, pero este artículo es una buena fuente de información: http://www.ibm.com/developerworks/java/library/j-jtp05236/

La cosa
fuente
0

Yo diría que en algunos casos está bien no hacer nada. Probablemente no sea algo que deba hacer de manera predeterminada, pero en caso de que no haya forma de que ocurra la interrupción, no estoy seguro de qué más hacer (probablemente 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 hilo de demonio manejando estas tareas y no interrumpa el hilo usted mismo (que yo sepa, jvm no interrumpe los hilos de demonio en el apagado de jvm), no veo forma de que ocurra la interrupción, y por lo tanto podría ser solo ignorado (Sí sé que el jvm puede matar un hilo de demonio en cualquier momento y, por lo tanto, no es adecuado en algunos casos).

EDITAR: Otro caso podría ser bloques protegidos, al menos basados ​​en el tutorial de Oracle en: http://docs.oracle.com/javase/tutorial/essential/concurrency/guardmeth.html

Bjarne Boström
fuente
Este es un mal consejo. Tengo problemas extremos para pensar en cualquier tarea que sea tan importante como para ignorar las interrupciones. Es probable que Oracle solo fuera perezoso y no quisiera agregar (más) desorden a la invocación Thread.sleep (). A menos que sepa por qué su hilo se está interrumpiendo, debe detener lo que esté haciendo (ya sea regresar o volver a lanzar una excepción para ayudar a que su hilo muera lo más rápido posible).
Ajax
Por eso dije a veces. También porque aún no se sugirió y, en mi opinión , está perfectamente bien en algunos casos. Supongo que depende del tipo de software que esté creando, pero si tiene un subproceso de trabajo 24/7 que nunca debería detenerse (aparte del cierre de JVM), trataría las interrupciones de, por ejemplo, tomar un elemento de un BlockingQueue como un error (es decir, iniciar sesión) ya que "no debería suceder" e intente nuevamente. De cource tendría banderas separadas para verificar la terminación del programa. En algunos de mis programas, el apagado de JVM no es algo que deba ocurrir en condiciones normales de funcionamiento.
Bjarne Boström
O dicho de otra manera: en mi opinión, es mejor arriesgarse a tener una declaración adicional de registro de errores (y, por ejemplo, que se envíe un correo en los registros de errores) que tener una aplicación que debería ejecutarse 24/7 para detenerse debido a una excepción de interrupción, para lo cual No veo forma de suceder durante condiciones normales.
Bjarne Boström
Hm, bueno, tus casos de uso pueden diferir, pero en un mundo ideal, no solo terminas porque te interrumpieron. Dejas de quitar el trabajo de la cola y dejas que ESTE hilo muera. Si el programador de subprocesos también se interrumpe y se le dice que cierre, entonces la JVM está finalizando y su juego ha terminado. En particular, si envía kill -9 o está intentando derribar su servidor de correo o lo que sea, lo ignorará hasta que fuerce el cierre, evitando así que otras partes de su código se apaguen correctamente. Forzar la muerte mientras se escribe en el disco o en la base de datos, y estoy seguro de que sucederán cosas maravillosas.
Ajax