La variable volatile dans la langue Java peut être considérée comme un "plus léger synchronisé"; Par rapport au bloc synchronisé, la variable volatile nécessite moins de codage et de surcharge d'exécution, mais la fonctionnalité qu'il peut atteindre n'est qu'une partie du synchronisé.
Verrouillage
Les verrous fournissent deux caractéristiques principales: l'exclusion mutuelle et la visibilité.
Variables volatiles
La variable volatile a les caractéristiques de visibilité du synchronisé, mais n'a pas les caractéristiques atomiques. Cela signifie que le thread peut découvrir automatiquement la dernière valeur de la variable volatile.
Les variables volatiles peuvent être utilisées pour assurer la sécurité du thread, mais ne peuvent être appliquées qu'à un ensemble très limité de cas d'utilisation: il n'y a pas de contrainte entre plusieurs variables ou entre la valeur actuelle d'une variable et la valeur modifiée. Par conséquent, l'utilisation de volatils seul n'est pas suffisant pour implémenter des compteurs, des mutexes ou toute classe avec des invariants associés à plusieurs variables (par exemple "start <= end").
Pour plus de simplicité ou d'évolutivité, vous pouvez avoir tendance à utiliser des variables volatiles au lieu des serrures. Certains idiomes sont plus faciles à coder et à lire lors de l'utilisation de variables volatiles au lieu des verrous. De plus, la variable volatile ne provoque pas de blocage du thread comme une serrure et provoque donc rarement des problèmes d'évolutivité. Dans certains cas, la variable volatile peut également fournir des avantages de performance sur les verrous si l'opération de lecture est beaucoup plus grande que l'opération d'écriture.
Conditions d'utilisation correcte des variables volatiles
Vous ne pouvez utiliser que la variable volatile au lieu des verrous dans des cas limités. Pour rendre la variable volatile de sécurité idéale de filetage, les deux conditions suivantes doivent être remplies en même temps:
Les opérations d'écriture en variables ne dépendent pas de la valeur actuelle.
Cette variable n'est pas incluse dans l'invariant avec d'autres variables.
En fait, ces conditions indiquent que ces valeurs valides qui peuvent être écrites à la variable volatile sont indépendantes de l'état de tout programme, y compris l'état actuel de la variable.
Les limites des premières conditions empêchent la variable volatile d'être utilisée comme compteur en filetage. Bien qu'une opération incrémentielle (X ++) ressemble à une opération distincte, il s'agit en fait d'une opération combinée composée d'une séquence d'opérations de lecture-modification-écriture qui doivent être effectuées atomiquement, et volatile ne peut pas fournir les propriétés atomiques nécessaires. La mise en œuvre de l'opération correcte nécessite de garder la valeur constante de X pendant l'opération, ce qui n'est pas possible avec la variable volatile. (Cependant, si la valeur est ajustée pour être écrite uniquement à partir d'un seul thread, la première condition peut être ignorée.)
La plupart des situations de programmation entrent en conflit avec l'une de ces deux conditions, ce qui rend la variable volatile non aussi universellement applicable à la sécurité des filetages comme synchronisée. Listing 1 montre une classe de plage numérique non sécurisée sans fil. Il contient un invariant - la limite inférieure est toujours inférieure ou égale à la borne supérieure.
Donner un exemple
Voyons un exemple ci-dessous. Nous mettons en œuvre un compteur. Chaque fois que le thread commence, la méthode Counter Inc sera appelée pour ajouter le compteur à l'environnement d'exécution - Version JDK: JDK1.6.0_31, mémoire: 3G CPU: x86 2.4G
classe publique Counter {public static int count = 0; public static void inc () {// Le retard ici est de 1 milliseconde, ce qui rend le résultat évident d'essayer {thread.sleep (1); } catch (InterruptedException e) {} count ++; } public static void main (String [] args) {// Démarrer 1000 threads en même temps pour effectuer des calculs i ++ et voir le résultat réel pour (int i = 0; i <1000; i ++) {nouveau thread (new Runnable () {@Override public void run () {compter.inc ();}}). start (); } // La valeur de chaque exécution ici peut être différente, peut-être 1000 System.out.println ("Run Result: Count.Count =" + COMPTER.COUNT); }} Résultat en cours: compteur.Count = 995
Le résultat de l'opération réel peut être différent à chaque fois. Le résultat de la machine est: Résultat de l'exécution: compteur.Count = 995. On peut voir que dans un environnement multi-thread, le nombre ne s'attend pas à ce que le résultat soit de 1000.
Beaucoup de gens pensent qu'il s'agit d'un problème de concurrence multi-thread. Il vous suffit d'ajouter volatile avant le nombre de variables pour éviter ce problème. Ensuite, nous modifions le code pour voir si le résultat répond à nos attentes.
classe publique Counter {public volatile static int count = 0; public static void inc () {// Le retard ici est de 1 milliseconde, ce qui rend le résultat évident d'essayer {thread.sleep (1); } catch (InterruptedException e) {} count ++; } public static void main (String [] args) {// Démarrer 1000 threads en même temps, effectuer des calculs i ++ et voir le résultat réel pour (int i = 0; i <1000; i ++) {nouveau thread (new Runnable () {@Override public void run () {compter.inc ();}}). start (); } // La valeur de chaque exécution ici peut être différente, peut-être 1000 System.out.println ("Run Result: Count.Count =" + COMPTER.COUNT); }}Résultat en cours: compteur.Count = 992
Le résultat de l'opération n'est toujours pas aussi 1000 que nous nous attendions. Analysons les raisons ci-dessous
Dans l'article de la collection Java Garbage, l'attribution de la mémoire au moment de JVM est décrite. L'une des zones de mémoire est la pile de machines virtuelles JVM. Chaque thread a une pile de threads lorsqu'il s'exécute, et la pile de thread enregistre les informations de valeur de la variable pendant les exécutions du thread. Lorsqu'un thread accède à la valeur d'un certain objet, il trouve d'abord la valeur de la variable correspondant à la mémoire du tas via la référence de l'objet, puis charge la valeur spécifique de la variable de mémoire du tas dans la mémoire locale du thread pour créer une copie variable. Après cela, le thread n'a plus de relation avec la valeur de la variable de mémoire du tas de l'objet, mais modifie directement la valeur de la variable de copie, et à un certain moment après la modification (avant que le thread ne quitte), il écrit automatiquement la valeur de la variable de thread Copy Retour à la variable de tas de l'objet. De cette façon, la valeur de l'objet dans le tas changera. L'image suivante décrit l'interaction de cette écriture
Variables de copie EAD et de chargement de la mémoire principale à la mémoire de travail actuelle
Utiliser et affecter le code d'exécution pour modifier la valeur de variable partagée
Stockez et rédigez un contenu lié à la mémoire principale avec des données de mémoire de travail
où l'utilisation et l'attribution peuvent apparaître plusieurs fois
Cependant, ces opérations ne sont pas atomiques, c'est-à-dire après la charge de lecture, si la variable de comptage de mémoire principale est modifiée, la valeur de la mémoire de travail du thread ne provoquera pas de modifications correspondantes depuis son chargement, de sorte que le résultat calculé sera différent de l'attendu.
Pour les variables modifiées par volatile, la machine virtuelle JVM garantit que la valeur chargée de la mémoire principale à la mémoire de travail du thread est la dernière
Par exemple, si le thread 1 et le thread 2 effectuent des opérations de lecture et de chargement, et constatez que la valeur du nombre dans la mémoire principale est de 5, alors la dernière valeur sera chargée
Une fois le nombre de tas modifié dans le thread 1, il sera écrit dans la mémoire principale et la variable de comptage dans la mémoire principale deviendra 6.
Étant donné que Thread 2 a déjà effectué l'opération de lecture et de charge, la valeur variable du nombre de mémoire principal sera également mise à jour à 6 après l'opération.
Cela provoque la concurrence après que deux threads soient modifiés avec le mot clé volatil dans le temps.