Históricamente, Java ha intentado proporcionar interrupciones preventivas limitadas, pero ha habido muchos problemas, como los Thread.stop, Thread.suspend y Thread.resume abandonados presentados anteriormente. Por otro lado, teniendo en cuenta la solidez del código de la aplicación Java, se reduce el umbral de programación y se reduce la probabilidad de que los programadores que no conocen el mecanismo subyacente dañen involuntariamente el sistema.
Hoy en día, la programación de subprocesos de Java no proporciona interrupciones preventivas, sino que utiliza interrupciones cooperativas. De hecho, el principio de interrupción cooperativa es muy simple: sondear una determinada marca que indica una interrupción. Podemos implementarlo en cualquier código ordinario.
Por ejemplo el siguiente código:
bool volátil está interrumpido;
//…
mientras (! está interrumpido) {
calcular();
}
Sin embargo, los problemas del código anterior también son obvios. Cuando el tiempo de ejecución del cálculo es relativamente largo, no se puede responder a la interrupción a tiempo. Por otro lado, al utilizar el sondeo para verificar las variables de marca, no hay forma de interrumpir las operaciones de bloqueo de subprocesos, como esperar y dormir.
Si aún usa la idea anterior, si desea que se responda a la interrupción de manera oportuna, debe verificar la variable de marca a través de la programación de subprocesos en la parte inferior de la máquina virtual. Sí, esto se hace en la JVM.
Lo siguiente es un extracto del código fuente de java.lang.Thread:
público estático booleano interrumpido() {
devolver hilo actual().isInterrupted(verdadero);
}
//…
el booleano nativo privado está interrumpido (booleano ClearInterrupted);
Se puede encontrar que isInterrupted se declara como un método nativo, que depende de la implementación subyacente de la JVM.
De hecho, la JVM mantiene un indicador de interrupción internamente para cada subproceso. Sin embargo, la aplicación no puede acceder directamente a esta variable de interrupción y debe operar mediante los siguientes métodos:
Hilo de clase pública {
//Establecer marca de interrupción
interrupción pública vacía () {...}
//Obtener el valor de la marca de interrupción
público booleano está interrumpido () {...}
//Borra la marca de interrupción y devuelve el valor de la última marca de interrupción
público estático booleano interrumpido() {...}
}
Normalmente, llamar al método de interrupción de un subproceso no provoca una interrupción inmediatamente, sino que solo establece el indicador de interrupción dentro de la JVM. Por lo tanto, al marcar el indicador de interrupción, la aplicación puede hacer algo especial o ignorar la interrupción por completo.
Puede pensar que si la JVM solo proporciona este tosco mecanismo de interrupción, básicamente no tiene ninguna ventaja en comparación con el método propio de la aplicación para definir variables de interrupción y sondeo.
La principal ventaja de las variables de interrupción internas de la JVM es que proporciona un mecanismo para simular "trampas de interrupción" automáticas para determinadas situaciones.
Al ejecutar llamadas de bloqueo que involucran la programación de subprocesos (como esperar, suspender y unirse), si ocurre una interrupción, el subproceso bloqueado arrojará InterruptedException "lo más rápido posible". Por lo tanto, podemos utilizar el siguiente marco de código para manejar las interrupciones de bloqueo de subprocesos:
intentar {
//espera, duerme o únete
}
captura (Excepción interrumpida e) {
//Algunos trabajos de manejo de interrupciones
}
Por "lo más rápido posible", supongo que la JVM verifica la variable de interrupción en el intervalo entre la programación de subprocesos. La velocidad depende de la implementación de la JVM y el rendimiento del hardware.
Sin embargo, para determinadas operaciones de bloqueo de subprocesos, la JVM no genera automáticamente InterruptedException. Por ejemplo, ciertas operaciones de E/S y operaciones de bloqueo interno. Para este tipo de operación, las interrupciones se pueden simular de otras formas:
1) E/S de socket asíncrono en java.io
Al leer y escribir sockets, los métodos de lectura y escritura de InputStream y OutputStream se bloquearán y esperarán, pero no responderán a las interrupciones de Java. Sin embargo, después de llamar al método de cierre del Socket, el hilo bloqueado generará una SocketException.
2) E/S asíncrona implementada mediante Selector
Si el hilo está bloqueado en Selector.select (en java.nio.channels), llamar al método de activación provocará una excepción ClosedSelectorException.
3) Adquisición de bloqueo
Si un hilo está esperando adquirir un bloqueo interno, no podemos interrumpirlo. Sin embargo, utilizando el método lockInterruptfully de la clase Lock, podemos proporcionar capacidades de interrupción mientras esperamos el bloqueo.
Además, en un marco donde las tareas y los subprocesos están separados, una tarea generalmente no sabe qué subproceso será llamada y, por lo tanto, no conoce la estrategia del subproceso que realiza la llamada para manejar las interrupciones. Por lo tanto, después de que la tarea establece el indicador de interrupción del subproceso, no hay garantía de que la tarea se cancele. Por tanto, existen dos principios de programación:
1) No debes interrumpir un hilo a menos que conozcas su política de interrupción.
Este principio nos dice que no debemos llamar directamente al método de interrupción de un hilo en un marco como Executer, sino que debemos usar métodos como Future.cancel para cancelar tareas.
2) El código de la tarea no debe adivinar qué significa la interrupción para el hilo de ejecución.
Este principio nos dice que cuando el código general encuentra una excepción interrumpida, no debe detectarla y "tragarla", sino que debe continuar arrojándola al código superior.
En resumen, el mecanismo de interrupción no preventiva en Java requiere que cambiemos la idea tradicional de interrupción preventiva y adoptemos los principios y patrones correspondientes para la programación basados en la comprensión de su esencia.