Les barrières mémoire, parfois appelées clôtures mémoire, sont des primitives essentielles dans la programmation concurrente Java pour gérer l'ordre et la visibilité des opérations mémoire entre threads.
Elles traitent les défis de visibilité mémoire et de réordonnancement d'instructions, particulièrement critiques sur les architectures processeurs multicœurs.
Classification des barrières mémoire
Quatre types principaux existent, chacun avec un rôle spécifique.
Barrière LoadLoad
Cette barrière garantit que toutes les lectures avant la barrière sont terminées avant d'effectuer les lectures après la barrière.
Lecture de variable_a
Lecture de variable_b
Barrière LoadLoad // Assure que les lectures de variable_a et variable_b sont complètes avant de lire variable_c et variable_d
Lecture de variable_c
Lecture de variable_d
Empêche le réordonnancement où variable_c ou variable_d pourrait être lu avant varible_a ou variable_b.
Utilisée pour les dépendances entre lectures, après une lecture volatile, ou lors de l'acquisition d'un verrou.
Barrière StoreStore
Assure que toutes les écritures avant la barrière sont rendues visibles avant d'effectuer les écritures après la barrière.
Écriture de mémoire_x
Écriture de mémoire_y
Barrière StoreStore // Garantit que les écritures de mémoire_x et mémoire_y sont visibles avant d'écrire mémoire_z et mémoire_w
Écriture de mémoire_z
Écriture de mémoire_w
Empêche que mémoire_z ou mémoire_w soit écrit avant la complétion des écritures précédentes.
Appliquée pour les dépendances entre écritures, avant une écriture volatile, ou avant la libération d'un verrou.
Barrière LoadStore
Garantit que les lectures avant la barrière sont achevées avant d'initier les écritures après la barrière.
Lecture de donnée_alpha
Lecture de donnée_beta
Barrière LoadStore // S'assure que les lectures de donnée_alpha et donnée_beta sont finies avant d'écrire donnée_gamma
Écriture de donnée_gamma
Empêche l'écriture de donnée_gamma d'être réordonnée avant les lectures.
Utile pour les patterns où une lecture conditionne une mise à jour, comme vérifier puis modifier.
Barrière StoreLoad
C'est la barrière la plus forte, assurant que toutes les écritures avant la barrière sont globalement visibles avant d'effectuer les lectures après la barrière.
Écriture de paramètre_1
Écriture de paramètre_2
Barrière StoreLoad // Force la visibilité des écritures de paramètre_1 et paramètre_2 avant de lire résultat_a et résultat_b
Lecture de résultat_a
Lecture de résultat_b
Empêche la lecture de résultat_a ou résultat_b avant que les écritures précédentes ne soient propagées.
Essentielle après une écriture volatile, après la libération d'un verrou, ou pour les dépendances écriture-lecture critiques.
Barrières mémoire dans les blocs synchronisés
L'acquisition d'un verrou via synchronized implique des barrières au début et à la fin du bloc.
// Acquisition du verrou
Barrières à l'entrée :
1. LoadLoad : Empêche les lectures à l'intérieur du bloc d'être réordonnées avant l'acquisition
2. LoadStore : Empêche les écritures dans le bloc d'être réordonnées avant les lectures initiales
// Exécution du code synchronisé
// Avant la libération du verrou
Barrières à la sortie :
1. StoreStore : Assure que toutes les écritures sont complètes et visibles
2. StoreLoad : Garantit que les lectures après le bloc voient les écritures effectuées à l'intérieur
Barrières mémoire avec le mot-clé volatile
Les opérations volatile en Java incluent des barrières spécifiques pour maintenir la visibilité et l'ordre.
// Écriture volatile :
1. Barrière StoreStore : Assure que les écritures normales précédentes sont terminées
2. Écriture volatile elle-même
3. Barrière StoreLoad : Rend l'écriture volatile immédiatement visible à tous les threads
// Lecture volatile :
1. Barrière LoadLoad : Assure que les lectures précédentes sont achevées
2. Lecture volatile elle-même
3. Barrière LoadStore : Empêche les écritures suivantes d'être réordonnées avant cette lecture