Prefacio
Para InterruptedException, una forma común de manejarlo es "tragarlo": atraparlo y no hacer nada (o grabarlo, pero eso no es mucho mejor), al igual que el Listado 4 más adelante. Desafortunadamente, este enfoque ignora el hecho de que las interrupciones pueden ocurrir durante este período, y las interrupciones pueden hacer que la aplicación pierda la capacidad de cancelar la actividad o cerrarla a tiempo.
Método de bloqueo
Cuando un método lanza una InterruptedException, no solo le dice que puede lanzar una excepción de verificación específica, sino que también le dice algo más. Por ejemplo, le dice que es un método de bloqueo, que intentará eliminar el bloqueo y el regreso lo antes posible si responde correctamente.
El método de bloqueo es diferente del método general que lleva mucho tiempo ejecutarse. La finalización de un método general depende solo de lo que esté haciendo y si hay suficientes recursos informáticos disponibles (ciclos de CPU y memoria). La finalización del método de bloqueo también depende de algunos eventos externos, como la expiración del temporizador, la finalización de E/S u acciones de otro hilo (libere un bloqueo, establezca un indicador o coloque una tarea en una cola de trabajo). Los métodos generales terminan después de que se realiza su trabajo, mientras que los métodos de bloqueo son más difíciles de predecir porque dependen de eventos externos. Los métodos de bloqueo pueden afectar la capacidad de respuesta porque es difícil de predecir cuándo terminarán.
El método de bloqueo puede no finalizarse porque no puede esperar el evento que está esperando, por lo que es muy útil hacer que el método de bloqueo sea cancelable (y a menudo es muy útil si los métodos sin bloqueo de larga duración son cancelables). Una operación cancelable se refiere a una operación que se puede terminar desde el exterior antes de la finalización normal. El mecanismo de interrupción proporcionado por Thread y soportado por Thread.sleep () y Object.Wait () es un mecanismo de cancelación; Permite que un hilo solicite otro hilo que detenga lo que está haciendo. Cuando un método lanza una InterruptEdException, le dice que si el hilo que ejecuta el método se interrumpe, intentará detener lo que está haciendo y regresar con anticipación, y al lanzar una Excepción InterruptedException, regresa de antemano. Un método de biblioteca de bloqueo bien por el comportamiento debe responder a las interrupciones y lanzar una Excepción InterruptedException para que pueda usarse en actividades cancelables sin afectar la respuesta.
Interrupción de hilo
Cada hilo tiene un atributo booleano asociado con él, que representa el estado interrumpido del hilo. El estado de interrupción es falso al principio; Cuando otro hilo interrumpe un hilo llamando a Thread.interrupt (), se produce una de las dos situaciones. Si ese hilo está ejecutando un método de bloqueo interrumpible de bajo nivel, como Thread.sleep (), Thread.Join () o Object.Wait (), desbloqueará y lanzará una Excepción InterruptedException. De lo contrario, la interrupción () solo establece el estado de interrupción del hilo. Después de que el código que se ejecuta en el hilo interrumpido puede encuestar el estado de interrupción para ver si se solicita que detenga lo que está haciendo. El estado de interrupción se puede leer por Thread.Isinterrupted () y puede ser leído y borrado mediante una operación llamada Thread.interrupted ().
La interrupción es un mecanismo de colaboración. Cuando un hilo interrumpe otro hilo, el hilo interrumpido no necesariamente detiene lo que está haciendo de inmediato. En cambio, una interrupción es una solicitud cortés a otro hilo para detener lo que está haciendo cuando está dispuesto y conveniente. Algunos métodos, como Thread.sleep (), se toman muy en serio tales solicitudes, pero cada método no necesariamente responde a las interrupciones. Para las solicitudes de interrupción, los métodos que no bloquean pero aún tardan mucho tiempo en ejecutar puede encuestar el estado de interrupción y regresar con anticipación cuando se interrumpe. Puede ignorar las solicitudes de interrupción a voluntad, pero hacerlo afectará la respuesta.
Un beneficio de la naturaleza colaborativa de las interrupciones es que proporciona una mayor flexibilidad para construir actividades cancelables de manera segura. Raramente queremos que una actividad se detenga de inmediato; Si la actividad se cancela mientras una actualización está en progreso, la estructura de datos del programa puede ser inconsistente. La interrupción permite que una actividad cancelable limpie el trabajo continuo, restaure a los invariantes, notifique a otras actividades que debe cancelarse antes de que se cancele.
Manejar interruptedException
Si lanzar una Excepción InterruptedException significa que un método es un método de bloqueo, llamar a un método de bloqueo significa que su método también es un método de bloqueo, y debe tener algún tipo de estrategia para manejar la Excepción InterruptedEx. La estrategia más fácil suele ser lanzar una interrupción de Excepción usted mismo, como se muestra en el código en los métodos PutTask () y getTask () en el Listado 1. Hacerlo hace que el método responda a las interrupciones y simplemente agregue una Excepción InterruptedException a la cláusula de lanzamientos.
Listado 1. No atrapar la Excepción InterruptedEx, propagándolo a la persona que llama
clase pública TaskQueue {private static final int max_tasks = 1000; Private Bloquingqueue <ark> queue = new LinkedBlowingqueue <ark> (max_tasks); public void PutTask (Tarea R) lanza interruptedException {queue.put (r); } Tarea pública getTask () lanza interruptedException {return queue.take (); }} A veces se requiere algunos trabajos de limpieza antes de propagarse la excepción. En este caso, puede atrapar la EXPERECIÓN DE INTERRUPTEDEX, realizar una limpieza y luego organizar una excepción. El Listado 2 demuestra esta técnica, un mecanismo utilizado para igualar a los jugadores en los servicios de juegos en línea. El método MatchPlayers () espera a que lleguen dos jugadores y luego comienza un nuevo juego. Si el método se interrumpe cuando ha llegado un jugador, pero otro jugador no ha llegado, volverá a colocar a ese jugador en la cola y volver a retirar la Excepción InterruptedException para que la solicitud del jugador al juego no se pierda.
Listado 2. Realice el trabajo de limpieza específico de la tarea antes de volver a crecer InterruptedException
Playermatcher de clase pública {jugadores de reproductores privados; Public Playermatcher (Playersource Players) {this.players = jugadores; } public void matchPlayers () lanza interruptedException {try {Player PlayerOne, playertwo; while (true) {playerOne = playertwo = null; // Espere a que lleguen dos jugadores y comiencen un nuevo juego jugadorMeOne = Players.WaitforPlayer (); // podría lanzar IE Playertwo = Players.WaitforPlayer (); // podría lanzar IE StartNewGame (PlayerOne, Playertwo); }} Catch (InterruptedException e) {// Si obtuvimos un jugador y fuimos interrumpidos, vuelva a colocar ese jugador if (jugadore! = null) jugadores.addfirst (jugadore); // luego propague la excepción de lanzamiento e; }}} No dejes de tragar vivo
A veces, lanzar una Excepción InterruptedException no es apropiada, por ejemplo, cuando una tarea definida por un ejecutable llama a un método interruptible. En este caso, la Excepción InterruptedException no se puede volver a crecer, pero tampoco quieres no hacer nada. Cuando un método de bloqueo detecta una interrupción y arroja una Excepción InterruptedEx, borra el estado de interrupción. Si se atrapa una Excepción Interrupted, pero no se puede rehacer, entonces la evidencia de la interrupción ocurre debe mantenerse para que el código de nivel superior en la pila de llamadas pueda conocer la interrupción y responder a ella. Esta tarea se puede hacer llamando a Interrupt () para "reinterrumpir" el hilo actual, como se muestra en el Listado 3. Al menos, siempre que se atrapa una Excepción Interrupted y no se vuelve a colocar, el hilo actual se vuelve a interrogar antes de regresar.
Listado 3. Reanudación de estado interrumpido después de capturar interrupción de Excepción
clase pública Taskrunner implementa Runnable {private Bloquingqueue <arke> queue; Public taskrunner (Bloquingqueue <Kark> queue) {this.queue = queue; } public void run () {try {while (true) {tarea task = queue.take (10, timeUnit.seconds); task.exCute (); }} Catch (InterruptedException e) {// restaurar el estado interrumpido hilo.currentThread (). interrupt (); }}} Lo peor que puede hacer cuando se trata de InterruptedException es tragarlo crudo: atraparlo y luego no volver a retirarlo ni volver a ascender en el estado de interrupción del hilo. Para una excepción con la que no sabe cómo tratar, la forma más estándar de manejar es atraparla y grabarlo, pero este método sigue siendo como una interrupción sin procesar, porque el código de nivel superior en la pila de llamadas aún no puede obtener información sobre la excepción. (No es aconsejable registrar interrupciones interrupcadas, porque es demasiado tarde para procesarlo cuando alguien viene a leer el registro). El listado 4 muestra un patrón ampliamente utilizado, que también es un patrón de interrupción de tragación en bruto:
Lista 4. Interrupción de deglución en bruto: no hagas esto
// No hagas esta clase pública TaskRunner implementa Runnable {private Bloquingqueue <Kark> queue; Public taskrunner (Bloquingqueue <Kark> queue) {this.queue = queue; } public void run () {try {while (true) {tarea task = queue.take (10, timeUnit.seconds); task.exCute (); }} Catch (interruptedException Swallowed) { / * No hagas esto - Restaurar el estado interrumpido en su lugar * /}}} Si no se puede rehacer la Excepción InterruptedEx, independientemente de si planea procesar la solicitud de interrupción o no, aún necesita volver a interrogar el hilo actual, ya que una solicitud de interrupción puede tener múltiples "receptores". La implementación de subprocesos de trabajadores estándar del grupo de hilos (ThreadPoolExeCutor) es responsable de la interrupción, por lo que interrumpir una tarea en un grupo de subprocesos en ejecución puede tener un doble efecto. Una es cancelar la tarea, y la otra es notificar al hilo de ejecución que el grupo de subprocesos está a punto de cerrarse. Si la tarea devora la solicitud, el hilo del trabajador no sabrá que hay una interrupción solicitada, retrasando el cierre de la aplicación o servicio.
Implementar tareas canceladas
No existe una semántica específica para las interrupciones en la especificación del lenguaje, pero en programas más grandes, es difícil mantener cualquier semántica de interrupción, excepto la cancelación. Dependiendo de cuál sea la actividad, los usuarios pueden solicitar la cancelación a través de una GUI o a través de un mecanismo de red, como JMX o servicio web. La lógica del programa también se puede solicitar que cancele. Por ejemplo, un rastreador web se cerrará automáticamente si detecta que el disco está lleno, de lo contrario, un algoritmo paralelo iniciará múltiples hilos para buscar diferentes áreas del espacio de solución y cancelar esos hilos una vez que uno de los hilos encuentre una solución.
El hecho de que una tarea sea cancelable no significa que una solicitud de interrupción deba responder de inmediato. Para las tareas que ejecutan código en un bucle, generalmente solo es necesario verificar si hay interrupciones una vez para cada iteración de bucle. Dependiendo de cuánto tiempo se ejecute el bucle, puede tardar algún tiempo en que cualquier código note que el hilo ha sido interrumpido (ya sea encuestar el estado de interrupción llamando al método Thread.isInterrupted () o llamar a un método de bloqueo). Si la tarea debe responder, puede encuestar el estado de interrupción con más frecuencia. El método de bloqueo generalmente encuesta el estado de interrupción inmediatamente en la entrada y, si está configurado para mejorar la capacidad de respuesta, también arroja una Excepción InterruptedException.
La única vez que puedes dejar de tragar vivo es que sabes que el hilo está a punto de salir. Este escenario solo ocurre cuando la clase que llama al método interrumpible es parte del hilo, no ejecutable o código de biblioteca general, como se muestra en el Listado 5. El Listado 5 crea un hilo que enumera los números Prime hasta que se interrumpe, lo que también se permite salir cuando se interrumpe. El bucle utilizado para buscar verificaciones de primos en busca de interrupciones en dos lugares: uno es sondear el método iSinterrupted () en la cabeza del bucle While, y el otro es llamar al método de bloqueo Bloquingqueue.put ().
Listado 5. Si sabe que el hilo está a punto de salir, puede tragarse la interrupción
Public Class PrimeProducer extiende el hilo {Bloqueo final privado de Bloquea <Biginteger> Queue; Primeproducer (Bloquingqueue <Biginteger> Queue) {this.queue = queue; } public void run () {try {biginteger p = bigInteger.one; while (! Thread.CurrentThread (). ISinterrupted ()) queue.put (p = P.NextProbablePrime ()); } catch (interruptedException consumed) { / * Permitir que el hilo salga * /}} public void cancel () {interrupt (); }} Método de bloqueo ininterrumpido
No todos los métodos de bloqueo arrojan interrupción. Las clases de flujo de entrada y salida bloquean que se completan la E/S, pero no lanzan InterruptedException y no volverán con anticipación si se interrumpen. Sin embargo, para la E/S del socket, si un rosca cierra el enchufe, la operación de E/S de bloqueo en ese enchufe terminará temprano y se lanza una explicación de Socket. Las clases de E/S sin bloqueo en Java.nio no admiten E/S interrumpibles, pero también se pueden desbloquear cerrando el canal o solicitando el despertar en el selector. Del mismo modo, no se puede interrumpir un bloqueo interno (ingresar un bloque sincronizado), pero Reentrantlock admite el modo de adquisición interrumpible.
Tareas no bloqueables
Algunas tareas se niegan a ser interrumpidas, lo que las hace no aboladas. Sin embargo, incluso las tareas no cancelables deben intentar preservar el estado de interrupción en caso de que el código de nivel superior en la pila de llamadas debe procesar las interrupciones después de que terminen las tareas no cancelables. El Listado 6 muestra un método que espera una cola de bloqueo hasta que aparezca un elemento disponible en la cola, independientemente de si se interrumpe o no. Por conveniencia, reanuda el estado de interrupción después del final en un bloque finalmente, para no privar a la persona que llama la solicitud de interrupción. (No puede reanudar el estado de interrupción anteriormente, porque eso causará un bucle infinito - bloqueoqueue.take () encuestará inmediatamente el estado de interrupción en la entrada y, si se encuentra el conjunto de estado de interrupción, se lanzará una Excepción InterruptedException).
Listado 6. Tareas incancelables que restauran el estado interrumpido antes de regresar
Tarea pública getNextTask (Bloquingqueue <ark> queue) {boolean interrumped = false; intente {while (true) {try {return queue.take (); } capt (interruptedException e) {interrupted = true; // cae y vuelve a intentar}}} Finalmente {if (interrupt) thread.currentThread (). interrupt (); }} Resumir
Puede usar el mecanismo de interrupción de colaboración proporcionado por la plataforma Java para construir estrategias de cancelación flexibles. Las actividades pueden decidir a su propia discreción si son cancelables o no son cancelables, y cómo responder a las interrupciones, y también pueden posponer las interrupciones si el retorno inmediato compromete la integridad de la aplicación. Incluso si desea ignorar las interrupciones por completo en su código, debe asegurarse de que el estado de interrupción se restablezca sin volver a crecerlo, para que el código que lo llama no sepa qué está sucediendo la interrupción. Lo anterior es el contenido completo de la teoría y la práctica de Java de manejar las excepciones de InterruptedException. Espero que este artículo te sea útil. Si tiene alguna pregunta, deje un mensaje para su discusión.