Prefacio
Anteriormente he compartido un artículo sobre el uso de @Async para implementar tareas asíncronas y control del grupo de subprocesos en Spring Boot: "Spring Boot usa @async para implementar llamadas asíncronas: grupo de hilos personalizado". Como recientemente descubrí muchos problemas causados por no manejar correctamente las tareas asíncronas, continuaré hablando sobre el elegante cierre de las piscinas de hilos, principalmente para los piscinas de hilos ThreadpoolTaskScheduler.
Fenómeno de problemas
En el ejemplo del Capítulo 4-1-3 del artículo anterior, definimos un grupo de hilos y luego escribimos 3 tareas usando la anotación @async, y especificamos el grupo de hilos utilizado por estas tareas para ejecutar. En la prueba unitaria anterior, no hablamos específicamente sobre problemas relacionados con el cierre. Simulemos un problema y lo creamos en el sitio.
Paso 1: Como antes, definimos una piscina de hilo de ThreadPoolTaskScheduler:
@SpringBootApplicationPublic Aplolation {public static void main (string [] args) {springApplication.run (application.class, args); } @Enableasync @configuration class taskpoolConfig {@Bean ("TaskExecutor") ejecutor público taskExecutor () {ThreadPoolTaskScheduler Ejecutor = nuevo ThreadPoolTaskScheduler (); ejecutor.setPoolSize (20); Ejecutor.setThreadNamePrefix ("TaskExecutor-"); devolver ejecutor; }}}Paso 2: Reforme la tarea asincrónica anterior y haga que confíe en un recurso externo, como Redis
@Slf4j @componentPublic class tarea {@aUtoWired private stringRedistEmplate stringRedistEmplate; @Async ("Taskexecutor") public void dotaskone () lanza la excepción {log.info ("Iniciar tarea uno"); Long Start = System.CurrentTimemillis (); log.info (stringRedistEmplate.randomkey ()); Long End = System.CurrentTimemillis (); log.info ("Tarea completa uno, Tiempo tomado:" + (end - inicio) + "milisegundos"); } @Async ("taskexecutor") public void dotasktwo () lanza la excepción {log.info ("Iniciar tarea 2"); Long Start = System.CurrentTimemillis (); log.info (stringRedistEmplate.randomkey ()); Long End = System.CurrentTimemillis (); log.info ("Tarea completa 2, Tiempo tomado:" + (End - Inicio) + "MilliseConds"); } @Async ("TaskExecutor") public void dotaskThree () lanza la excepción {log.info ("Inicie la tarea tres"); Long Start = System.CurrentTimemillis (); log.info (stringRedistEmplate.randomkey ()); Long End = System.CurrentTimemillis (); log.info ("Tarea completa tres, consumir tiempo:" + (end - inicio) + "milisegundos"); }}Nota: Los pasos para introducir dependencias y configurar redis en pom.xml se omiten aquí
Paso 3: Modifique la prueba unitaria y simule la situación de apagado en alta concurrencia:
@Runwith (SpringJunit4ClassRunner.class) @SpringBoOttestPublic AplicationTests {@aUtoWired Tarea privada Tarea; @Test @sneakythrows public void test () {for (int i = 0; i <10000; i ++) {task.Dotaskone (); task.DotaskTwo (); task.DotaskThree (); if (i == 9999) {System.exit (0); }}}}Nota: Al pasar a través del bucle for, envía tareas al grupo de subprocesos definidos anteriormente. Dado que se ejecuta asincrónicamente, durante la ejecución, use System.exit (0) para cerrar el programa. En este momento, dado que se están ejecutando tareas, puede observar si la destrucción de estas tareas asincrónicas y el orden de otros recursos en el contenedor de primavera es segura.
Paso 4: Ejecute la prueba unitaria anterior y encontraremos el siguiente contenido de excepción.
org.springframework.data.redis.redisconnectionfailureException: no se puede obtener la conexión JEDIS; La excepción anidada es redis.clients.jedis.exceptions.jedisconnectionException: no pudo obtener un recurso del grupo en org.springframework.data.redis.connection.jedis.jedisconnectionFactory.fetchjedisconnector (jedisconnectionFactory.Java:204) ~ [Spring-Data-Redis-1.8.10.Release.Jar: Na] en org.springframework.data.redis.connection.jedis.jedisconnectionFactory.getConnection (jedisconnectionFactory.java:348) ~ [spring-data-acredis-1.8.10.release.jar: na] org.springframework.data.redis.core.redisconnectionutils.dogetconnection (redisconnectionUtils.java:129) ~ [spring-data-redis-1.8.10.release.jar: na] a org.springframework.data.redis.core.redisconnectionutils.getConnection (redisconnectionUtils.java:92) ~ [spring-data-redis-1.8.10.release.jar: na] en org.springframeward.data.redis.core.redisconnections.getConnection (redisconnection (redisconnection) ~ ~ [Spring-data-Redis-1.8.10.Release.Jar: Na] en org.springframework.data.redis.core.redistemplate.execute (redistemplate.java:194) ~ [spring-data-acredis-1.8.10.reelease.JAR: nail org. ~ [Spring-Data-Redis-1.8.10.Release.Jar: Na] en com.didispace.async.task.Dotaskone (tarea org.springframework.cglib.proxy.methodproxy.invoke (métodos org. org.springframework.aop.framework.reflectivemethodinvocation.proced (reflectivemethodinVoced.java:157) ~ [Spring-AOP-4.3.14.Release.jar: 4.3.14.Release] org.springframework.aop.interceptor.asyncexecutionInterceptor $ 1.call (asyncexecutionInterceptor.java:115) ~ [Spring-AOP-4.3.14.Release.jar: 4.3.14.Release] en java.util.concurrent.futuraTask.Run (FUTURETASK.JAVA:266) [na: 1.8.0_151] en java.util.concurrent.scheduledthreadpoolexecutor $ programadofuturetask.access $ 201 (programado de lactancia java.util.concurrent.scheduledThreadPoolExecutor $ programadofuturetask.run (programado [na: 1.8.0_151] en java.util.concurrent.threadpoolexecutor $ trabajador.run (threadpoolexecutor.java:624) [na: 1.8.0_151] en java.lang.thread.run (horth.java:748) [na: 1.8.0_151] causado por: redis.clients.jedis.exceptions.jedisconnectionException: no pudo obtener un recurso del grupo en redis.clients.util.pool.getresource (piscina ~ [JEDIS-2.9.0.JAR: NA] en redis.clients.jedis.jedispool.getresource (jedispool.java:16) ~ [jedis-2.9.0.jar: na] en org.springframework.data.redis.connection.jedis.jedisconnectionFactory.fetchJedisconnector (jedisconnectionFactory.java:194) ~ [Spring-data-redis-1.8.10.release.jar: na] ... 19 tramas comunes omitidos por: jave.lang.interruptedException java.util.concurrent.locks.AbstractqueedSynChronizer $ condicionObject.ReportInterruptAfterWait (AbstractqueedSynChronizer.java:2014) ~ [na: 1.8.0_151] en java.util.concurrent.locks.AbstractQueueDynChronizer $ condicionObject.AWaitnanos (abstractqueedSynChronizer.java:2088) ~ [na: 1.8.0_151] en org.apache.commons.pool2.ImlinkedBlockingDequ.pollfirst (LinkedBlockingDequpocking.Java:63555555) ~ [Commons-Pool2-2.4.3.Jar: 2.4.3] en org.apache.commons.pool2.impl.genericObjectpool.BorrowObject (genicObjectpool.java:442) ~ [Commons-pool2-2.4.3.jar: 2.4.3] AT org.apache.commons.pool2.impl.genericObjectpool.BorrowObject (genericObjectpool.java:361) ~ [commons-pool2-4.3.jar: 2.4.3] en redis.clients.util.pool.getresource (piscina
Cómo resolverlo
Análisis de causa
De la息JedisConnectionException: Could not get a resource from the pool , es fácil para nosotros pensar que la tarea asíncrona aún se está ejecutando cuando la aplicación está cerrada. Dado que el grupo de conexión Redis se destruye primero, el error anterior se informa en la tarea asincrónica para acceder a Redis. Entonces, concluimos que el método de implementación anterior no es elegante cuando la aplicación está cerrada, entonces, ¿qué debemos hacer?
Solución
Es muy simple resolver el problema anterior. ThreadPoolTaskScheduler de Spring nos proporciona configuraciones relevantes, solo agregue la siguiente configuración:
@Bean ("TaskExecutor") ejecutor público taskexecutor () {threadpoolTaskScheduler ejecutor = new ThreadPoolTaskScheduler (); ejecutor.setPoolSize (20); Ejecutor.setThreadNamePrefix ("TaskExecutor-"); Ejecutor.SetWaitFortAkStocompleteonShutdown (verdadero); ejecutor.setAwaitterMinationseconds (60); devolver ejecutor;} Nota: setWaitForTasksToCompleteOnShutdown(true) Este método es la clave aquí. Se usa para establecer el grupo de subprocesos para esperar a que todas las tareas se completen antes de continuar destruyendo otros frijoles. De esta manera, la destrucción de estas tareas asincrónicas será precedida por la destrucción del grupo de hilos Redis. Al mismo tiempo, SetaWaitterMinationseconds (60) también se establece aquí. Este método se utiliza para establecer el tiempo de espera de las tareas en el grupo de subprocesos. Si excede esta vez, se verá obligado a destruirlo antes de que se destruya para garantizar que la aplicación pueda cerrarse al final en lugar de bloquearse.
Ejemplo completo:
Los lectores pueden elegir los siguientes dos repositorios para ver los proyectos del Capítulo 4-1-4 de acuerdo con sus preferencias:
Github: https://github.com/dyc87112/springboot-lelarning/
Gitee: https://gitee.com/diidispace/springboot-lelarning/
Descarga local: http://xiazai.vevb.com/201805/yuanma/springboot-lelarning(vevb.com).rar
Resumir
Lo anterior es todo el contenido de este artículo. Espero que el contenido de este artículo tenga cierto valor de referencia para el estudio o el trabajo de todos. Si tiene alguna pregunta, puede dejar un mensaje para comunicarse. Gracias por su apoyo a Wulin.com.