Cómo implementar tareas a largo plazo en JAVA
1. Antecedentes del problema En las aplicaciones, a menudo necesitamos una clase para completar tareas como procesamiento de datos, escuchar eventos o verificar las actividades de otra clase. Para lograr este objetivo, podríamos usar hilos con un conjunto de bloqueos y notificaciones de mensajes. La API de subprocesos JAVA está bien documentada, pero para que los subprocesos se ejecuten de manera correcta y eficiente, los programadores aún necesitan una amplia experiencia en programación y escribir mucho código. Al aplicar el marco analizado en este artículo, los programadores pueden crear rápidamente aplicaciones sólidas sin tener que soportar la molestia de escribir grandes cantidades de código. 2. Marco para tareas de larga duración Lo principal de una tarea de larga duración es que de alguna manera debe mantenerse ejecutándose durante la vida útil de la aplicación. La forma correcta de lograr esto es proporcionar un hilo de ejecución para una tarea particular que usted cree. una tarea como un hilo o como una implementación de la interfaz java.lang.Runnable. Si implementa Runnable, puede obtener un mejor diseño orientado a objetos y evitar los problemas de herencia única. También puede manipular de manera más eficiente las instancias de Runnable. Por ejemplo, usar un grupo de subprocesos que normalmente necesita una instancia Runnable, no un subproceso, para ejecutarse. Lo principal de las tareas de ejecución prolongada es cómo mantenerlas ejecutándose durante toda la vida útil de la aplicación. La forma adecuada de implementar esto es proporcionar un hilo para realizar esta tarea específica. Podemos lograr este objetivo heredando la clase Thread o implementando la interfaz java.lang.Runnable. Si adopta el método de implementar la interfaz Runnable, puede obtener un mejor diseño orientado a objetos y evitar el problema de herencia única en JAVA. Además, también podemos manejar instancias Runnable de manera más eficiente (por ejemplo, el uso de un grupo de subprocesos generalmente requiere una instancia Runnable en lugar de un subproceso para ejecutarse).
La esencia del marco es la clase abstracta Worker (Listado A), que implementa la interfaz Runnable y proporciona los métodos auxiliares para el manejo eficiente de tareas. Algunos de los métodos están completamente implementados, como el método run(), pero algunos son abstractos y Debe completarlo usted mismo. Si desea crear una clase de larga duración, solo necesita extender la clase Worker e implementar varios métodos abstractos. Veamos estos métodos con más detalle. Worker Class, que implementa la interfaz Runnable y proporciona una buena manera de manejar tareas de manera eficiente. Algunos de estos métodos se han implementado, como el método run(), pero algunos son métodos abstractos y los desarrolladores deben implementarlos ellos mismos. Si desea crear una clase de larga duración, solo necesita heredar la clase Worker e implementar algunos métodos abstractos. Veamos los detalles de estos métodos. El método run() de la clase Worker está diseñado para ejecutar continuamente el método work() hasta que se detenga. El método work() puede ser responsable del procesamiento de datos, la reacción a algún evento, la lectura o escritura de archivos, la ejecución de SQL, etc. Puede generar una excepción, por lo que es una buena práctica propagarla y dejar que el método run() la maneje. El método run() de la clase Worker está diseñado para continuar ejecutando el método work() mientras lo haga. no dejar de correr. El método work() puede ser responsable del procesamiento de datos, respuesta a eventos, lectura y escritura de archivos, ejecución de comandos SQL y otras operaciones. De esta manera, el método work() puede generar excepciones y pasarlas a run(), y luego el método run() manejará estas excepciones.
El método run() tiene dos niveles de cláusula try-catch: fuera y dentro del bucle while. La primera cláusula try-catch está destinada a detectar todas las excepciones no programadas y garantizar que el método run() nunca salga. detectar cualquier tipo de excepción perteneciente a la lógica empresarial y comportarse en consecuencia. Si se produce alguna operación de espera en el método work() (por ejemplo, esperar en un InputStream o un Socket), es recomendable propagar una InterruptedException. Tenga en cuenta que el método work() no necesita tener ningún bucle while para que siga funcionando mientras se ejecute una aplicación. El método run() tiene dos capas de declaraciones try-catch: una. dentro y uno fuera Fuera del bucle while, una capa está dentro del bucle while. El try-catch anterior se utiliza para detectar excepciones que no son de programación para garantizar que el método run() no salga. La última declaración try-catch captura varias excepciones relacionadas con la lógica empresarial y el comportamiento correspondiente. Si se produce alguna operación de espera en el método work() (como esperar un flujo de entrada o un Socket), es recomendable lanzar una InterruptedException. Lo que hay que recordar es que mientras la aplicación se esté ejecutando, el método work() no necesita ningún bucle while para seguir funcionando, todo esto lo hace el trabajador. Cuando se inicia el método run(), llama a prepareWorker(), que está diseñado para preparar todos los recursos necesarios para una tarea de larga duración (Listado A). En esta llamada de método, puede, por ejemplo, establecer una conexión de base de datos o abrirla. un archivo que se utilizará más adelante. Es especialmente bueno colocar aquí algunas operaciones de bloqueo, como abrir un socket, porque se realizarán en un hilo separado y, por lo tanto, no bloquearán el hilo principal de ejecución. Cuando se inicie run(), llame. método prepareWorker() para preparar todos los recursos necesarios para la tarea de larga duración (consulte el Listado A). Por ejemplo, en este método puede abrir una conexión de base de datos o un archivo que se utilizará. Esto es especialmente útil para bloquear operaciones como establecer un socket.
Porque si se ejecutan en un hilo separado, no bloquearán la ejecución del hilo principal. Lo opuesto al método anterior es releaseWorker(), que se llama cuando el método run() está a punto de salir (Listado A aquí, puede colocar el código para deshacerse de los recursos del sistema utilizados por esta tarea o para realizar otra limpieza). Este método es similar a java.lang.Object.finalize(), pero se llama explícitamente antes de que finalice un hilo. Lo opuesto al método anterior es releaseWorker(), que se llama cuando el método run() está listo para salir. (consulte la lista de programas A). En este método, puede escribir código que libere recursos del sistema o realice otras acciones de limpieza. Este método es similar a java.lang.Object.finalize(), pero se llama explícitamente cuando finaliza el hilo. 3. Manejo de errores en el marco Otro método importante es handleError(), que toma java.lang.Throwable como parámetro. Este método se llama cada vez que se produce una situación de error dentro del método run(). Depende de usted cómo. Para implementar el manejo de errores, una forma es registrar errores y controlar la finalización de la tarea llamando al método halt() (Listado A. El método isCondition() se utiliza para indicar si se puede iniciar la ejecución del método work(), permitiendo así la granularidad). control sobre una tarea. Es útil en marcos activados por eventos cuando la ejecución del método work() está pendiente hasta que se cumpla alguna condición (por ejemplo, un búfer no está vacío). En la implementación de Worker, la condición se verifica en un bloqueo. notificación y periódicamente con un intervalo de tiempo que especifique en el método setTimeout() (Listado A). Si no necesita ningún bloque de espera en una tarea, simplemente haga que el método isCondition() siempre devuelva verdadero. Se utiliza para determinar si el método work() se puede ejecutar.
Esto permite un control detallado de las tareas. Esto es útil en marcos desencadenados por eventos. Cuando no se cumplen las condiciones de ejecución del método work (), el método de trabajo se suspenderá hasta que las condiciones se cumplan por completo (por ejemplo, el búfer no está vacío). En la implementación del Worker, esta condición buscará periódicamente una notificación de bloqueo a la hora especificada en el método setTimeout(). Si no necesita ningún bloqueo de espera en la tarea, simplemente haga que el método isCondition() siempre devuelva verdadero. 4. Momento de finalización de la tarea