Comprendre le mécanisme
Des tâches concurrentes peuvent partager un état et entrelacer leurs opérations. volatile apporte des garanties de visibilité et d’ordre, pas l’atomicité d’une séquence comme count++. Utilisez verrou ou type atomique pour un invariant adapté. Un ExecutorService gère le lancement de tâches ; Future porte le résultat ou l’échec qu’il faut effectivement observer. Des collections concurrentes ne rendent pas automatiquement atomique une séquence de plusieurs appels.
Raisonner et vérifier
Les virtual threads de Java 21 aident à servir de nombreuses tâches principalement bloquantes. Ils ne multiplient pas le nombre de cœurs ni n’accélèrent magiquement un calcul CPU. Limitez l’accès aux ressources rares avec un mécanisme explicite. Dans Java 21, un bloc synchronized contenant une attente bloquante peut aussi être pertinent pour analyser le pinning ; raisonnez toujours avec la version ciblée.
Visibilité et atomicité ne se confondent pas
Un champ volatile apporte des garanties de visibilité et d’ordre définies par le modèle mémoire. Il ne transforme pas toute séquence en opération atomique. Un incrément comme count++ comporte une lecture, un calcul et une écriture ; deux threads peuvent lire le même ancien nombre. Définissez la propriété à protéger, puis choisissez une primitive atomique ou un verrou adapté, au lieu de supposer que la visibilité suffit.
Erreur fréquente à comprendre
volatile rend automatiquement compteur++ atomique.
Lecture, calcul et écriture peuvent rester des étapes distinctes.
Protéger un invariant composé
Si un traitement doit vérifier un solde puis le modifier avec une autre donnée, protéger séparément chaque lecture ne rend pas automatiquement la transition entière cohérente. La zone de synchronisation doit correspondre à l’invariant partagé. Dans un service singleton, évitez aussi de conserver le marchand courant dans un champ mutable réutilisé par toutes les requêtes. Des paramètres locaux et immuables limitent ce partage involontaire de contexte.
Erreur fréquente à comprendre
Un champ volatile suffit toujours à plusieurs mises à jour liées.
L’atomicité du groupe demande une stratégie adaptée.
Observer les résultats des tâches
Un Future peut représenter un résultat encore attendu, réussi ou en échec selon son cycle. Recevoir cet objet ne prouve pas que le travail métier a abouti. Le consommateur doit observer les résultats et les erreurs, puis appliquer une politique de reprise ou de refus. Abandonner silencieusement les références peut laisser des échecs invisibles tandis que l’appelant annonce déjà un succès.
Erreur fréquente à comprendre
Obtenir un Future prouve déjà le succès du travail.
Le résultat peut être en attente ou en échec.
Gérer la durée des exécuteurs
Un exécuteur possède des ressources et un cycle de fermeture. En créer sans politique peut multiplier threads, files et travail restant. Définissez qui le possède et quand il cesse d’accepter des tâches. Un test doit couvrir la fin normale, une erreur et l’arrêt avec travail en attente. Le fait qu’un processus termine rapidement dans l’IDE ne prouve pas que toutes les ressources sont libérées proprement.
Erreur fréquente à comprendre
Créer des pools indéfiniment ne consomme aucune ressource.
Threads et files demandent une gestion de durée de vie.
Annuler n’efface pas tout effet
Une demande d’annulation ne garantit pas un arrêt immédiat de chaque calcul ou de toute opération distante déjà validée. Le code doit coopérer selon le mécanisme utilisé et les dépendances peuvent continuer leur travail. Pour une mutation sensible, conservez une référence et un moyen de réconciliation. Un timeout ou une annulation côté client ne démontre pas que le service externe a laissé ses données intactes.
Erreur fréquente à comprendre
cancel force toujours la fin immédiate de tout effet distant.
Le traitement et les dépendances peuvent continuer.
Traiter le signal d’interruption
L’interruption signale une demande à laquelle le code doit répondre selon son contrat. La capturer puis l’ignorer systématiquement peut empêcher un arrêt cohérent. Examinez la responsabilité du niveau courant et la façon de conserver ou propager l’information utile. Les tests doivent provoquer l’interruption pendant une attente contrôlée et vérifier que le traitement libère les ressources prévues sans annoncer un succès qui n’a pas eu lieu.
Erreur fréquente à comprendre
Une interruption doit toujours être silencieusement ignorée.
Le code doit préserver une politique d’arrêt cohérente.
Threads virtuels et ressources réelles
Les threads virtuels Java 21 facilitent notamment de nombreuses tâches comportant des attentes bloquantes indépendantes. Ils ne créent pas de puissance CPU ni une capacité infinie chez un fournisseur. Pour limiter cinq opérations simultanées vers une ressource, un Semaphore peut représenter cette borne avec libération garantie. Définissez aussi la file et le délai : multiplier les tâches en attente ne retire pas le besoin de budgets.
Erreur fréquente à comprendre
Un thread virtuel multiplie automatiquement la puissance CPU.
Ils ne créent pas de cœurs supplémentaires.
Opérations concurrentes et workflow composé
Une collection concurrente protège des opérations selon son contrat, pas nécessairement toute combinaison de plusieurs appels. Lire une valeur puis décider une écriture peut encore former une course. Cherchez une opération composée adaptée ou une synchronisation portant sur l’invariant entier. Un test utile interpose deux acteurs entre la lecture et l’écriture, au lieu de vérifier seulement plusieurs appels exécutés successivement.
Erreur fréquente à comprendre
Deux opérations séparées deviennent atomiques car la Map est concurrente.
Un invariant composé peut demander une primitive spécifique.
Borner la file et la pression
Une file sans limite peut accumuler plus de travail que le service ne peut traiter dans le délai utile. La mémoire et la latence deviennent alors des ressources consommées avant même l’exécution. Choisissez capacité, refus et reprise selon le produit. Une limitation de concurrence sans limitation d’attente peut seulement déplacer le problème ; mesurez ensemble tâches actives, tâches en file et durée totale du parcours.
Erreur fréquente à comprendre
Une file infinie est une garantie universelle de disponibilité.
L’accumulation peut épuiser la mémoire et la latence.
Tester les entrelacements pertinents
Un succès séquentiel ne démontre pas l’absence d’une course. Identifiez l’endroit où deux acteurs peuvent observer ou modifier le même état, puis contrôlez cette interposition avec des mécanismes de test appropriés. Évitez de dépendre uniquement de pauses aléatoires. Vérifiez l’invariant final, les erreurs et les effets externes ; augmenter seulement le nombre d’itérations ne remplace pas un scénario qui cible réellement la frontière.
Erreur fréquente à comprendre
Un succès séquentiel prouve l’absence de tout interleaving dangereux.
Les exécutions concurrentes peuvent produire d’autres ordres.
Atelier guidé : lire, expliquer et vérifier le code
class Counter {
volatile int value;
void increment() { value++; }
} Le champ volatile du compteur ne suffit pas à rendre l’incrément composé indivisible. Deux threads peuvent lire une même valeur, calculer chacun la suivante et écrire le même résultat, perdant ainsi une augmentation. Il ne s’agit pas d’une erreur de syntaxe ni d’une conversion implicite vers AtomicInteger : le champ reste un int et les étapes du calcul restent à analyser.
Dans un laboratoire, comparez une primitive d’incrément atomique avec un invariant portant sur plusieurs champs. La première peut convenir au simple compteur, alors que le second demande une protection de groupe. Ajoutez un scénario de service singleton avec un contexte marchand passé en paramètre. Vérifiez que deux appels concurrents ne se contaminent pas, même lorsque leurs étapes sont volontairement entrelacées.
Singleton Spring : état de requête et concurrence
@Service
public class PaymentService {
private String merchantId;
public void pay(String id) {
merchantId = id;
// ... utilise ensuite merchantId
}
} Un bean singleton est partagé dans le conteneur concerné. Si un champ mutable currentMerchant reçoit le marchand de chaque demande, deux threads peuvent se remplacer cette valeur pendant leur traitement. Le second marchand peut alors être utilisé pour une opération du premier.
Conservez le contexte propre à l’appel dans une variable locale ou une commande immuable, puis transmettez-le explicitement aux dépendances. Ce modèle rend la relation visible et évite de stocker une identité ambiante dans un objet partagé. Changer simplement le nom du champ ou rendre sa lecture plus rapide ne résout pas la course.
Un test concurrent synchronisé peut suspendre A après l’affectation, faire avancer B, puis reprendre A pour révéler le mélange. Vérifiez références prestataire et tenant sur les appels factices. Une structure immuable reste insuffisante si elle contient un objet interne mutable partagé ; examinez les références qui composent réellement le contexte et gardez l’autorisation à la frontière métier.
Points de vigilance : L’état propre à une requête ne doit pas vivre dans un champ mutable partagé entre traitements. volatile apporte de la visibilité mémoire, pas une isolation par requête. Un champ static élargit le partage au lieu de le supprimer. L’isolation transactionnelle des données ne garantit pas celle des champs en mémoire.
Documentation de référence ↗Exemple commenté
try (var executor = java.util.concurrent.Executors.newVirtualThreadPerTaskExecutor()) {
var result = executor.submit(() -> "R1");
System.out.println(result.get());
}Le résultat est attendu et les erreurs d’exécution ne sont pas ignorées. La fermeture de l’exécuteur traite sa terminaison selon son contrat.
Exercice de mise en pratique
Simulez cent appels bloquants, puis imposez au plus cinq accès simultanés à une dépendance limitée.
Voir une proposition de correction
Utilisez un Semaphore autour de l’accès rare avec libération dans finally. Lancez les tâches dans l’exécuteur, attendez leurs Future et vérifiez le maximum observé.
Points de vérification
- Tester la concurrence avec plusieurs tâches réelles.
- Observer les exceptions des Future.
- Ne pas confondre visibilité et opération atomique.
Contrôle de connaissances
Cours associé : Concurrence, exécuteurs et virtual threads ↗Validation du module
101 questions, objectif pédagogique de 75 %. Durée conseillée de 150 minutes, sans limite imposée. Les questions et les choix sont mélangés ; le corrigé apparaît après validation. Le contrôle rapide de la leçon et l’examen final du cours restent des exercices distincts.
Commencer ou reprendre les 101 questions →Cette marque suit votre propre avancement ; elle ne certifie pas la maîtrise du sujet. Effectuez l’exercice avant de passer à la suite.
