Préface
J'ai déjà partagé un article sur l'utilisation de @async pour implémenter des tâches asynchrones et un contrôle de pool de threads dans Spring Boot: "Spring Boot utilise @async pour implémenter les appels asynchrones: Pool de thread personnalisé". Depuis que j'ai récemment découvert de nombreux problèmes causés par le fait de ne pas manipuler correctement les tâches asynchrones, je continuerai à parler de la fermeture élégante des piscines de fil, principalement pour les piscines de fil ThreadPoolTasksCheduler.
Phénomène de problème
Dans l'exemple du chapitre 4-1-3 de l'article précédent, nous avons défini un pool de threads, puis écrit 3 tâches à l'aide de l'annotation @async, et spécifié le pool de thread utilisé par ces tâches pour exécuter. Dans le test unitaire ci-dessus, nous n'avons pas spécifiquement parlé de problèmes liés à l'arrêt. Simulons un problème et venons-le sur place.
Étape 1: Comme auparavant, nous définissons une piscine ThreadPoolTasksCheduler:
@SpringBootApplicationPublic class Application {public static void main (String [] args) {springApplication.run (application.class, args); } @Enableasync @configuration class taskpoolconfig {@bean ("taskexecutor") public exécutor taskexecutor () {threadpooltaskscheduler exécutor = new ThreadPoolTasksCheduler (); Executor.SetPoolSize (20); EMECTOR.SetThreadNamePrefix ("TaskExEcutor-"); return exécuteur; }}}Étape 2: Réformer la tâche asynchrone précédente et le faire s'appuyer sur une ressource externe, comme redis
@ Slf4j @ ComponentPublic Class tâche {@autowired private stringRedistemplate stringRedistemplate; @Async ("taskexecutor") public void dotaskone () lève une exception {log.info ("Démarrer la tâche un"); Long start = System.CurrentTimemillis (); log.info (stringredistemplate.randomKey ()); Long End = System.Currenttimemillis (); log.info ("Tâche complète un, temps pris:" + (end - démarrer) + "millisecondes"); } @Async ("taskexecutor") public void dotasktwo () lève une exception {log.info ("Démarrer la tâche 2"); Long start = System.CurrentTimemillis (); log.info (stringredistemplate.randomKey ()); Long End = System.Currenttimemillis (); Log.info ("Tâche complète 2, temps pris:" + (end - start) + "millisecondes"); } @Async ("taskexecutor") public void dotaskThere () lève l'exception {log.info ("Démarrer la tâche trois"); Long start = System.CurrentTimemillis (); log.info (stringredistemplate.randomKey ()); Long End = System.Currenttimemillis (); Log.info ("Tâche complète trois, qui prend du temps:" + (end - start) + "millisecondes"); }}Remarque: les étapes pour introduire les dépendances et configurer Redis dans pom.xml sont omises ici
Étape 3: Modifiez le test unitaire et simulez la situation d'arrêt en haute concurrence:
@Runwith (springjunit4classrunner.class) @springboottestpublic class applicationTests {@autowired tâche privée tâche; @Test @sneakythrows public void test () {for (int i = 0; i <10000; i ++) {task.dotaskone (); tâche.dotasktwo (); tâche.dotaskThere (); if (i == 9999) {System.exit (0); }}}}Remarque: En faisant une boucle à travers la boucle FOR, vous soumettez des tâches au pool de thread défini ci-dessus. Puisqu'il est exécuté de manière asynchrone, pendant l'exécution, utilisez System.exit (0) pour fermer le programme. Pour le moment, comme il y a des tâches exécutées, vous pouvez observer si la destruction de ces tâches asynchrones et l'ordre d'autres ressources dans le conteneur de printemps sont sûres.
Étape 4: Exécutez le test unitaire ci-dessus et nous rencontrerons le contenu d'exception suivant.
org.springframework.data.redis.redisconnectionfailureException: Impossible d'obtenir la connexion Jedis; Exception imbriquée est redis.clients.jedis.exception.JeDisconnectionException: Impossible d'obtenir une ressource de la piscine à org.springframework.data.redis.connection.Jedis.JeDisconnectionFactory.FetchJeDisconnector (JedeconnectionFactory.java:204) ~ [Spring-Data-redis org.springframework.data.redis.connection.jedis.jedeconnectionfactory.getConnection (JedisconnectionFactory.java:348) ~ [printemps-data-redis-1.8.10.release.jar: na] à org.springframework.data.redis.core.redisconnectionutils.dogetconnection (redisconnectionutils.java:129) ~ [printemps-data-redis-1.8.10.release.jar: na] at org.springframework.data.redis.core.redisconnectionutils.getConnection (redisconnectionutils.java:92) ~ [printemps-data-redis-1.8.10.release.jar: na] à org.springframework.data.redis.core.redisconnectiontutiles.getConnection (redisconnectils.java:79). ~ [printemps-data-redis-1.8.10.release.jar: na] à org.springframework.data.redis.core.redistemplate.execute (redetemplate.java:194) ~ [printemps-data-redis-1.8.10.release.jar: na] à la org.springframework.data.redis.core.redistemplate.execute (reidemplate.java:169) ~ [printemps-data-redis-1.8.10.release.jar: na] à org.springframework.data.redis.core.redistemplate.randomkey (redistemplate.java:781) ~ [printemps-data-redis-1.8.10.release.jar: na] à com.didispace.async.task.dotaskone (task.java:26) ~ [classes /: na] à com.didispace.async.task $$ fassclassBysPringCGLIB $$ CA3FF9d6.invoke (<généré>) ~ [Classes /: Na] à CA3FF9d6.invoke (<généré>) ~ [Classes /: Na] à CA3FF9D6.Invoke (<Generated org.springframework.cglib.proxy.methodproxy.invoke (methodproxy.java:204) ~ [printemps-core-4.3.14.release.jar: 4.3.14.release] à l'adresse org.springframework.aop.framework.cglibaopproxy $ cglibMethodinvocation.invokejoinpoint (cglibaopproxy.java:738) ~ [printemps-aop-4.3.14.release.jar: 4.3.14.release] à la org.springframework.aop.framework.refleclemethodinvocation.proceed (réflevismethodinvocation.java:157) ~ [printemps-aop-4.3.14.release.jar: 4.3.14.release] à l'affaire org.springframework.aop.interceptor.asyncexecutionInterceptor $ $ (asycexecutioninterceptor.java:115) ~ [printemps-aop-4.3.14.release.jar: 4.3.14.release] à java.util. [Na: 1.8.0_151] sur java.util.concurrent.scheduledThreadpoolExecutor $ planifiéefutureTask.Access 201 $ (ScheduledThreadPoolExecutor.java:180) [NA: 1.8.0_151] à AT sur java.util.concurrent.scheduledThreadpoolExecutor $ planifiée scheduledfutureTask.Run (ScheduledThreadPoolExecutor.java:293) [Na: 1.8.0_151] à java.util.concurrent.threadpoolExecutor.Runworker (ThreadpoolExecUtor.Java:1149) [na: 1.8.0_151] sur java.util.concurrent.threadpoolExecutor $ worker.run (threadpoolexecutor.java:624) [na: 1.8.0_151] à java.lang.thread.run (thread.java:748) [na: 1.8.0_151] Bust par: redis.clients.jedis.exception.JeDisconnectionException: Impossible d'obtenir une ressource de la piscine à redis.clients.util.pool.getResource (pool.java:53) ~ [jedis-2.9.0.jar: na] à redis.clients.jedis.Jedispool.getRaSource (Jedispool.java:226) ~ [jedis-2.9.0.jar: na] à redis.clients.jedis.jedispool.getResource (jedispool.java:16) ~ [Jedis-2.9.0.jar: na] at org.springframework.data.redis.connection.jededis.jedeconnectionfactory.fetchjeDisconnector (JedisconnectionFactory.java:194) ~ [printemps-data-redis-1.8.10.release.jar: na] ... 19 Frames communs OmitSedus By: java.lang. java.util.concurrent.locks.abstractqueeEdSynchronizer $ conditionobject.reportterruptafterwait (abstractqueuedynchronizer.java:2014) ~ [na: 1.8.0_151] à l'adresse java.util.concurrent.locks.abstractqueeEdSynchronizer $ conditionobject.awaitnanos (abstractqueuedynchronizer.java:2088) ~ [na: 1.8.0_151] sur org.apache.commons.pool2.impl.linkedBlockingDeg.pollfirst (lienblocking ~ [Commons-Pool2-2.4.3 org.apache.commons.pool2.impoll.genericObjectpool.borrowObject (genericObjectpool.java:361) ~ [Commons-Pool2-2.4.3: 2.4.3] sur redis.clients.util.pool.getResource (Pool.java:49) ~ [Jedis-2.9.0.Jar: Na] ... 22 Frages Common OMMITE OMMITE
Comment le résoudre
Analyse des causes
D'après les息JedisConnectionException: Could not get a resource from the pool , il est facile pour nous de penser que la tâche asynchrone est toujours en train de s'exécuter lorsque l'application est fermée. Étant donné que le pool de connexions Redis est d'abord détruit, l'erreur ci-dessus est signalée dans la tâche asynchrone pour accéder à Redis. Nous avons donc conclu que la méthode de mise en œuvre ci-dessus n'est pas élégante lorsque l'application est fermée, alors que devons-nous faire?
Solution
Il est très simple de résoudre le problème ci-dessus. ThreadPoolTasksCheduler de Spring nous fournit des configurations pertinentes, il suffit d'ajouter les paramètres suivants:
@Bean ("taskexecutor") exécuteur public taskexecutor () {threadpoolTasksCheduler exécutor = new ThreadPoolTasksCheduler (); Executor.SetPoolSize (20); EMECTOR.SetThreadNamePrefix ("TaskExEcutor-"); EMECTOR.SetWaitFortAkStoCleteLeShutdown (true); exécuteur.setAwitterminationsCondes (60); return exécuteur exécuteur;} Remarque: setWaitForTasksToCompleteOnShutdown(true) Cette méthode est la clé ici. Il est utilisé pour définir la piscine de fil pour attendre que toutes les tâches se terminent avant de continuer à détruire d'autres haricots. De cette façon, la destruction de ces tâches asynchrones sera précédée par la destruction de la piscine de thread Redis. Dans le même temps, SEAWIDERMINATIONSECONDS (60) est également défini ici. Cette méthode est utilisée pour définir le temps d'attente des tâches dans le pool de threads. S'il dépasse cette fois, il sera obligé de le détruire avant d'être détruit pour s'assurer que l'application peut être fermée à la fin plutôt que bloquée.
Exemple complet:
Les lecteurs peuvent choisir les deux référentiels suivants pour afficher les projets du chapitre4-1-4 en fonction de leurs préférences:
Github: https://github.com/dyc87112/springboot-learning/
Gitee: https://gitee.com/diidispace/springboot-learning/
Téléchargement local: http://xiazai.vevb.com/201805/yuanma/springboot-learning(vevb.com).rar
Résumer
Ce qui précède est l'intégralité du contenu de cet article. J'espère que le contenu de cet article a une certaine valeur de référence pour l'étude ou le travail de chacun. Si vous avez des questions, vous pouvez laisser un message pour communiquer. Merci pour votre soutien à wulin.com.