Traitement asynchrone Java
Avant d'examiner Spring @Async, il est essentiel de comprendre les concepts de synchronisme, d'asynchronisme et de multithreading. En supposant que vous connaissiez ces concepts, examinons la méthode de traitement asynchrone Java pure via du code. Si vous êtes familier avec les threads Java, vous pouvez ignorer ce chapitre.
Si vous créez une fonction qui reçoit un message et l'affiche simplement de manière synchrone, vous pouvez écrire le code comme ci-dessus. En le modifiant en méthode asynchrone multithread, le code source peut être écrit comme suit.
Cependant, cette méthode est très dangereuse car elle ne permet pas de gérer les threads. Par exemple, si 10 000 appels sont effectués simultanément, 10 000 threads doivent être créés en très peu de temps. Le coût de création de threads n'est pas négligeable et peut avoir un impact négatif sur les performances du programme, voire entraîner une erreur OOM. Par conséquent, pour gérer les threads, vous devez implémenter une pool de threads, et Java fournit la classe ExecutorService à cet effet.
Le nombre total de threads est limité à 10, et nous avons réussi à effectuer correctement le traitement asynchrone de la manière multithread souhaitée. Cependant, avec cette méthode, la méthode submit() de ExecutorService doit être appliquée à chaque méthode que nous souhaitons traiter de manière asynchrone, ce qui nécessite des modifications répétitives. En d'autres termes, si nous voulons modifier une méthode initialement écrite de manière synchrone en asynchrone, une modification de la logique de la méthode elle-même est inévitable.
Spring @Async
Méthode simple
Il suffit d'ajouter l'annotation @EnableAsync au-dessus de la classe Application et l'annotation @Async au-dessus de la méthode de logique synchrone que vous souhaitez traiter de manière asynchrone. Cependant, cette méthode présente le problème de ne pas gérer les threads. En effet, la configuration par défaut de @Async utilise SimpleAsyncTaskExecutor, qui ne sert pas de pool de threads, mais simplement à créer des threads.
Méthode utilisant un pool de threads
Tout d'abord, supprimez @EnableAsync de la classe Application. Si la classe Application est configurée avec @EnableAutoConfiguration ou @SpringBootApplication, elle lit les informations sur le bean threadPoolTaskExecutor (que nous créerons ci-dessous) de la classe SpringAsyncConfig (configurée avec @Configuration) au moment de l'exécution.
Vous pouvez définir les tailles de base et maximale. Dans ce cas, vous vous attendez à ce que le système fonctionne avec la taille de base initialement et que le nombre de threads augmente jusqu'à la taille maximale si le traitement devient impossible, mais ce n'est pas le cas.
En interne, une LinkedBlockingQueue de taille Integer.MAX_VALUE est créée, et si les threads de base ne peuvent pas traiter les tâches, ils attendent dans la file d'attente. Lorsque la file d'attente est pleine, le système crée des threads jusqu'à la taille maximale pour traiter les tâches.
Si vous trouvez que la taille de la file d'attente Integer.MAX_VALUE est trop importante, vous pouvez définir queueCapacity. Avec la configuration ci-dessus, le système traite les tâches avec 3 threads initialement, et si la vitesse de traitement est lente, les tâches sont mises en attente dans une file d'attente de 100 éléments. Si davantage de demandes arrivent, le système crée jusqu'à 30 threads pour traiter les tâches.
Une fois la configuration du pool de threads terminée, vous devez simplement ajouter le nom de ce bean dans la méthode annotée avec @Async.
Si vous souhaitez définir plusieurs types de pools de threads, créez plusieurs méthodes de création de beans telles que threadPoolTaskExecutor() et spécifiez le bean du pool de threads souhaité lors de la configuration de @Async.
Forme de retour selon le type de retour
Cas où il n'y a pas de valeur de retour
C'est le cas où la méthode à traiter de manière asynchrone n'a pas besoin de renvoyer de résultat de traitement. Dans ce cas, le type de retour de l'annotation @Async doit être défini sur void.
Cas où il y a une valeur de retour
Les types Future, ListenableFuture et CompletableFuture peuvent être utilisés comme types de retour. La forme de retour de la méthode asynchrone peut être encapsulée dans new AsyncResult().
[Future]
future.get() joue le rôle d'attente jusqu'à ce que le résultat de la requête arrive via un blocage. Cela rend la méthode asynchrone bloquante, ce qui réduit les performances. En général, Future n'est pas utilisé.
[ListenableFuture]
ListenableFuture permet de traiter les tâches de manière non bloquante via des callbacks. La première méthode du paramètre addCallback() définit la méthode de callback de fin de tâche, et la deuxième méthode définit la méthode de callback d'échec de tâche. Notez que, comme nous avons défini le nombre de threads de base du pool de threads sur 3, vous pouvez voir que le message « Task Start » est affiché 3 fois au début.
[CompletableFuture]
Bien que ListenableFuture permette d'implémenter une logique non bloquante, si des callbacks imbriqués sont nécessaires, cela conduit à un code très complexe appelé « enfer des callbacks ».
<span class="image-inline ck-widget" contenteditable="false"><img src="https://www.notion.so/image/https%3A%2F%2Fs3-us-west-2.amazonaws.com%2Fsecure.notion-static.com%2F9f152db8-c015-43cd-bf13-85c594f5f218%2FUntitled.png?table=block&id=268ac0bc-ca7b-4bcb-b11b-ac611a5038b2&spaceId=b453bd85-cb15-44b5-bf2e-580aeda8074e&width=2000&userId=80352c12-65a4-4562-9a36-2179ed0dfffb&cache=v2" alt="Untitled" style="aspect-ratio:2000/1455;" width="2000" height="1455"></span>
Bien sûr, nous n'allons pas aborder CompletableFuture en détail dans cette session, donc si vous êtes curieux de connaître le code qui gère les callbacks complexes, veuillez consulter le lien source ci-dessous.
La lisibilité est meilleure que celle de la définition de callback de ListenableFuture, et il assure parfaitement la fonctionnalité non bloquante. Par conséquent, lorsqu'il est nécessaire de renvoyer une valeur avec @Async, il est recommandé d'utiliser CompletableFuture.
Avantages de @Async
Les développeurs peuvent écrire des méthodes de manière synchrone et, s'ils souhaitent les rendre asynchrones, ils n'ont qu'à ajouter l'annotation @Async au-dessus de la méthode. Cela permet de créer du code facile à maintenir, tant pour le synchronisme que pour l'asynchronisme.
Précautions concernant @Async
Pour utiliser la fonction @Async, vous devez déclarer l'annotation @EnableAsync. Si vous ne configurez rien de plus, elle fonctionne en mode proxy. En d'autres termes, toutes les méthodes asynchrones fonctionnant avec l'annotation @Async suivent les contraintes de Spring AOP. Pour plus d'informations, veuillez consulter « cet article ».
- Même si vous ajoutez @Async à une méthode privée, AOP ne fonctionnera pas.
- Si des méthodes d'un même objet s'appellent entre elles, AOP ne fonctionnera pas.
Commentaires0