Prefácio
Anteriormente, compartilhei um artigo sobre o uso do @ASYNC para implementar tarefas assíncronas e controle do pool de threads na bota da primavera: "O Spring Boot usa @async para implementar chamadas assíncronas: pool de threads personalizado". Como descobri recentemente muitos problemas causados por não lidar com tarefas assíncronas corretamente, continuarei a falar sobre o elegante fechamento de pools de threads, principalmente para os pools ThreadPoolTaskScheduler Threads.
Problema fenômeno
No exemplo, o capítulo 4-1-3 do artigo anterior, definimos um pool de threads e depois escrevemos 3 tarefas usando a anotação @async e especificamos o pool de threads usado por essas tarefas para executar. No teste de unidade acima, não falamos especificamente sobre questões relacionadas ao desligamento. Vamos simular um problema e inventá -lo no local.
Etapa 1: Como antes, definimos um pool ThreadPoolTaskScheduler Threads:
@SpringbooTApplicationPublic Classe Application {public static void main (string [] args) {springapplication.run (application.class, args); } @Enableasync @configuration classe TaskPoolConfig {@Bean ("Taskexecutor") Public Executor Taskexecutor () {ThreadPoolTaskScheduler Executor = new ThreadPoolTaskScheduler (); executor.setPoolSize (20); Executor.setThreadNamePrefix ("Taskexecutor-"); executor de retorno; }}}Etapa 2: reforma a tarefa assíncrona anterior e faça depender de um recurso externo, como Redis
@Slf4j @componentpublic classe tarefa {@aUTowired Private StringRedReDistemplate stringReDISTEMPLATE; @Async ("taskexecutor") public void Dotaskone () lança exceção {log.info ("Start Task One"); long start = system.currenttimemillis (); log.info (stringredistemplate.randomKey ()); Long end = System.currenttimemillis (); log.info ("Tarefa completa um, tempo gasto:" + (end - start) + "milissegundos"); } @Async ("taskexecutor") public void Dotasktwo () lança a exceção {log.info ("start Task 2"); long start = system.currenttimemillis (); log.info (stringredistemplate.randomKey ()); Long end = System.currenttimemillis (); log.info ("Tarefa completa 2, tempo gasto:" + (final - start) + "milissegundos"); } @Async ("taskexecutor") public void Dotaskthree () lança exceção {log.info ("Start Task Three"); long start = system.currenttimemillis (); log.info (stringredistemplate.randomKey ()); Long end = System.currenttimemillis (); log.info ("Tarefa completa três, demorada:" + (end - start) + "milissegundos"); }}Nota: As etapas para introduzir dependências e configurar o Redis em Pom.xml são omitidas aqui
Etapa 3: Modifique o teste de unidade e simule a situação de desligamento em alta concorrência:
@Runwith (springjunit4classrunner.class) @springboottestpublic class ApplicationTests {@AUTOWIRED TARK PRIVADA TASK; @Test @sneakythrows public void test () {for (int i = 0; i <10000; i ++) {task.dotaskone (); tarefa.Dotasktwo (); tarefa.Dotaskthree (); if (i == 9999) {System.exit (0); }}}}Nota: Ao percorrer o loop for, você envia tarefas ao pool de threads definido acima. Como é executado de forma assíncrona, durante a execução, use System.Exit (0) para fechar o programa. No momento, como existem tarefas sendo executadas, você pode observar se a destruição dessas tarefas assíncronas e a ordem de outros recursos no contêiner da primavera é segura.
Etapa 4: Execute o teste de unidade acima e encontraremos o seguinte conteúdo de exceção.
org.springframework.data.redis.redisconnectionFailureException: não é possível obter a conexão JEDIS; nested exception is redis.clients.jedis.exceptions.JedisConnectionException: Could not get a resource from the pool at org.springframework.data.redis.connection.jedis.JedisConnectionFactory.fetchJedisConnector(JedisConnectionFactory.java:204) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.connection.jedis.jedisconnectionFactory.getConnection (jedisconnectionFactory.java:348) ~ [Spring-Data-Redis-1.8.10.release.jar: na] em org.springframework.data.redis.core.redisconnectionUtils.DogetConnection (Redisconnectionutils.java:129) ~ [Spring-Data-Redis-1.8.10.release.jar: Na] em org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:92) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.core.RedisConnectionUtils.getConnection(RedisConnectionUtils.java:79) ~ [Spring-Data-Redis-1.8.10.release.jar: na] em org.springframework.data.redis.core.redistemplate.execute (redistemplate.java:194) ~ [spring-data-redis-18.10.release.jar: na] org.springframework.data.redis.core.RedisTemplate.execute(RedisTemplate.java:169) ~[spring-data-redis-1.8.10.RELEASE.jar:na] at org.springframework.data.redis.core.RedisTemplate.randomKey(RedisTemplate.java:781) ~ [Spring-Data-Redis-1.8.10.release.jar: na] em com.didispace.async.task.dotaskone (task.java:26) ~ [classes/: na] em com.didispace.async.tak $$ fassclassbyspringcglib $$ ca3fispace org.springframework.cglib.proxy.methodproxy.invoke (Methodproxy.java:204) ~ [Spring-core-4.3.14.release.jar: 4.3.14.Release] em org.springframework.aop.framework.cglibaopproxy $ cglibmethodinvocation.invokeJoinPoint (cglibaopproxy.java:738) ~ [Spring-AOP-4.3.14.Release.Jar: 4.3.14.Release] at org.springframework.aop.framework.reflectiveMethodinvocation.proeced (refleteMethodinvocation.java:157) ~ [spring-aop-4.3.14.release.jar: 4.3.14.Release] em org.springframework.aop.intercept.asyncexecutionInterceptor $ 1.CALL (AsyncexecutionIntercept.java:115) ~ [spring-aop -4.3.14.release.jar: 4.3.14.release] at java.util.uCurrent.futureTask.runk (FuturetTen] em java.util.utilCurrent.futureTask.run (FuturetTen] em java.util.util.Current.futureTask.run (FuturetTen] em java.util.util.FURANT.66 [NA: 1.8.0_151] em java.util.concurrent.scheduledThreadPoolExecutor $ scheduledfutureTask.access $ 201 (ScheduledThreadpoolexecutor.java:180) [na: 1.8.0_151] Java.util.concurrent.scheduledThreadPoolExecutor $ scheduledfutureTask.run (scheduledthreadpoolExecutor.java:293) [na: 1.8.0_151] em java.util.concurrent.threadEngOdor.151] em java.util.concurrent.ThreadEngOdor.151] [na:1.8.0_151] at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_151] at java.lang.Thread.run(Thread.java:748) [na:1.8.0_151]Caused by: Redis.clients.jedis.exceptions.jedisconnectionException: Não foi possível obter um recurso da piscina em redis.clients.util.pool.getResource (pool.java:53) ~ [jedis-2.9.0.jar: nA] at Redis.clients.jedis.jedispool.getResource: ~ [jedis-2.9.0.jar: na] em redis.clients.jedis.jedispool.getResource (jedispool.java:16) ~ [jedis-2.9.0.jar: na] em org.springframework.data.redis.connection.jedis.jedisconnectionFactory.FetchJedisconnector (JedisconnectionFactory.java:194) ~ [Spring-Data-Redis-1.8.10.release.jar: Na] ... 19 Frames comuns: NAVELIVELAUS-1.8.10. java.util.concurrent.locks.abstractQueedsynchronizer $ condicioneObject.ReportInterruptAfterwait (abstractQueedsynchronizer.java:2014) ~ [NA: 1.8.0_151] em java.util.concurrent.locks.abstractQueedsynchronizer $ condyObject.awaitnanos (abstratequeedsynchronizer.java:2088) ~ [na: 1.8.0_151] em org.apache.commons.pool2.impl.linkedBlockingdequ. ~ [Commons-Pool2-2.4.3.Jar: 2.4.3] em org.apache.commons.pool2.impl.genericobjectpool.borrowObject (genicobjectpool.java:442) ~ [commons-Pool2-2.4.3.Jar: 2.4.3] em org.apache.commons.pool2.impl.genericObjectpool.bororrowObject (genicobjectpool.java:361) ~ [commons-poteol2-2.4.3.Jar: 2.4.3] em Redis.cliients.util.pool.GeReResource (pool.java:49) ~ [
Como resolvê -lo
Análise de causa
A partir das息JedisConnectionException: Could not get a resource from the pool , é fácil pensarmos que a tarefa assíncrona ainda está executando quando o aplicativo é fechado. Como o pool de conexão Redis é destruído primeiro, o erro acima é relatado na tarefa assíncrona para acessar o Redis. Então, concluímos que o método de implementação acima não é elegante quando o aplicativo é fechado, então o que devemos fazer?
Solução
É muito simples resolver o problema acima. O ThreadPoolTaskScheduler da Spring nos fornece configurações relevantes, basta adicionar as seguintes configurações:
@Bean ("Taskexecutor") Executor público Taskexecutor () {ThreadPoolTaskScheduler Executor = new ThreadPoolTaskScheduler (); executor.setPoolSize (20); Executor.setThreadNamePrefix ("Taskexecutor-"); Executor.SetWaitForTaskstocompleteOnshutdown (true); Executor.setAwaitMerminações -segundos (60); Retornar executor;} Nota: setWaitForTasksToCompleteOnShutdown(true) Este método é a chave aqui. É usado para definir o pool de threads para esperar que todas as tarefas sejam concluídas antes de continuar a destruir outros feijões. Dessa maneira, a destruição dessas tarefas assíncronas será precedida pela destruição do pool de roscas Redis. Ao mesmo tempo, o SetawaitMerminationseConds (60) também é definido aqui. Este método é usado para definir o tempo de espera das tarefas no pool de threads. Se exceder esse tempo, será forçado a destruí -lo antes de ser destruído para garantir que o aplicativo possa ser fechado no final, em vez de bloqueado.
Exemplo completo:
Os leitores podem escolher os dois repositórios a seguir para visualizar os projetos Capítulo 4-1-4 de acordo com suas preferências:
Github: https://github.com/dyc87112/springboot-learning/
Gitee: https://gitee.com/diidispace/springboot-learning/
Download local: http://xiazai.vevb.com/201805/yuanma/springboot-learning(vevb.com).rar
Resumir
O acima é o conteúdo inteiro deste artigo. Espero que o conteúdo deste artigo tenha certo valor de referência para o estudo ou trabalho de todos. Se você tiver alguma dúvida, pode deixar uma mensagem para se comunicar. Obrigado pelo seu apoio ao wulin.com.