volatile est un modificateur de variable qui garantit la visibilité des modifications de cette variable à tous les threads. Si une variable est déclarée comme volatile, sa lecture et son écriture se font directement dans la mémoire principale, contournant les caches locaux des threads. Cela empêche la mise en cache des valeurs localement dans le thread.
synchronized est un mot-clé qui assure non seulement la visibilité, mais aussi l'exclusion mutuelle (mutual exclusion) : seul un thread peut exécuter un bloc synchronisé à un moment donné pour un objet donné.
On doit utiliser des variables volatile pour de simples drapeaux et compteurs si :
Exemple d'utilisation de volatile :
class Flag { private volatile boolean running = true; public void stop() { running = false; } public void loop() { while (running) { // ... } } }
Peut-on utiliser volatile pour assurer l'atomiсité de l'incrémentation d'une variable ?
Réponse :
Non, volatile n'assure pas l'atomiсité des opérations. Par exemple, counter++ n'est pas une opération atomique même si counter est déclaré comme volatile, car l'opération implique la lecture, la modification et l'écriture (plusieurs actions) qui peuvent être interrompues par un autre thread. L'atomiсité est assurée par synchronized ou par des classes du paquet java.util.concurrent.atomic.
Exemple :
class Counter { private volatile int count = 0; public void increment() { count++; // PAS une opération atomique ! } }
Histoire
Dans un projet de magasin en ligne, le drapeau d'achèvement du thread de traitement des commandes a été réalisé sans volatile. En conséquence, un des threads "se coinçait" dans une boucle infinie, car il ne voyait pas le changement de variable effectué par un autre thread. Le diagnostic a pris plusieurs jours.
Histoire
Dans un système financier, un développeur a utilisé volatile int pour le compteur d'opérations. En période de forte charge, le nombre d'opérations a commencé à "disparaître". La raison est la perte d'atomicité lors d'opérations d'incrémentation complexes.
Histoire
Les développeurs ont confondu volatile et synchronized, essayant d'utiliser volatile pour garantir l'exclusion mutuelle d'accès aux sections critiques, ce qui a conduit à des data races et à des bogues difficiles à détecter dans l'application multithread.