Je lis quelque part en dessous de la ligne.
Le mot clé volatile Java ne signifie pas atomique, l'idée fausse selon laquelle, après avoir déclaré volatile, l'opération
++
sera atomique, vous devez toujours assurer un accès exclusif à l'aide de la méthode ou du blocsynchronized
en Java. .
Alors que se passera-t-il si deux threads attaquent une variable primitive volatile
en même temps?
Cela signifie-t-il que quiconque se verrouille dessus définira d'abord sa valeur? Et si entre temps, un autre thread monte et lit l'ancienne valeur alors que le premier thread changeait de valeur, le nouveau thread ne lira-t-il pas son ancienne valeur?
Quelle est la différence entre Atomic et le mot clé volatile?
L'effet du mot clé volatile
est que chaque opération de lecture ou d'écriture individuelle sur cette variable est atomique.
Cependant, une opération nécessitant plus d’une lecture/écriture - telle que i++
, qui équivaut à i = i + 1
, qui permet de lire et d’écrire - est non atomique, puisqu'un autre thread peut écrire sur i
entre la lecture et l'écriture.
Les classes Atomic
, telles que AtomicInteger
et AtomicReference
, fournissent une plus grande variété d'opérations de manière atomique, notamment l'incrément pour AtomicInteger
.
Volatile et Atomic sont deux concepts différents. Volatile garantit qu'un certain état (mémoire) attendu est vrai sur différents threads, tandis qu'Atomics garantit que l'opération sur les variables est effectuée de manière atomique.
Prenons l'exemple suivant de deux threads en Java:
Fil A:
value = 1;
done = true;
Fil B:
if (done)
System.out.println(value);
En commençant par value = 0
et done = false
, la règle de threading nous indique qu'il n'est pas défini si Thread B affichera ou non une valeur. En outre , la valeur n'est pas définie à ce stade également! Pour l'expliquer, vous devez en savoir un peu plus sur Java. gestion de la mémoire (qui peut être complexe), en bref: les threads peuvent créer des copies locales de variables et la machine virtuelle Java peut réorganiser le code pour l'optimiser. Par conséquent, rien ne garantit que le code ci-dessus est exécuté exactement dans cet ordre. Définir les valeurs true et alors la valeur 1 pourrait être un résultat possible des optimisations JIT.
volatile
s'assure seulement qu'au moment de l'accès à une telle variable, la nouvelle valeur sera immédiatement visible par tous les autres threads et l'ordre d'exécution garantit que le code est au bon endroit. Etat que vous vous attendez à ce qu'il soit. Donc, dans le cas du code ci-dessus, définir done
comme volatile garantira que chaque fois que Thread B vérifie la variable, elle est fausse ou true, et si c'est vrai, alors value
a également été défini sur 1.
En tant qu'effet secondaire de volatile , la valeur d'une telle variable est définie de manière atomique dans l'ensemble du thread (à un coût très minime en vitesse d'exécution). Ceci est toutefois important uniquement sur les systèmes 32 bits que i.E. utilisez des variables longues (64 bits) (ou similaires), dans la plupart des autres cas, définir/lire une variable est de toute façon atomique. Mais il existe une différence importante entre un accès atomique et une opération atomique. Volatile garantit uniquement que l'accès est atomique, tandis qu'Atomics veille à ce que l'opération le soit de manière atomique.
Prenons l'exemple suivant:
i = i + 1;
Quelle que soit la manière dont vous définissez i, un thread différent lisant la valeur juste au moment où la ligne ci-dessus est exécutée peut obtenir i, ou i + 1, car l'opération n'est pas atomiquement. Si l'autre thread attribue une valeur différente à i, dans le pire des cas, je pourrais être redéfini sur ce qu'il était auparavant par le thread A, car il était en train de calculer i + 1 en fonction de l'ancienne valeur, puis de définir i à nouveau à cette ancienne valeur + 1. Explication:
Assume i = 0
Thread A reads i, calculates i+1, which is 1
Thread B sets i to 1000 and returns
Thread A now sets i to the result of the operation, which is i = 1
Atomics, comme AtomicInteger, garantit que de telles opérations ont lieu de manière atomique. Donc, le problème ci-dessus ne peut pas se produire, je serais soit 1000 ou 1001 une fois les deux threads terminés.
Il existe deux concepts importants dans l'environnement multithreading.
Volatile
élimine le problème de visibilité mais ne traite pas de l'atomicité. Volatile
empêchera le compilateur de réorganiser l'instruction impliquant l'écriture et la lecture ultérieure d'une variable volatile. par exemple. k++
Ici k++
n'est pas une seule instruction machine, mais bien trois instructions machine.
Ainsi, même si vous déclarez variable à volatile
, l'opération ne sera pas atomique, ce qui signifie qu'un autre thread peut voir un résultat intermédiaire qui est une valeur périmée ou indésirable pour l'autre thread.
Mais AtomicInteger
, AtomicReference
sont basés sur instruction de comparaison et d'échange . CAS comporte trois opérandes: un emplacement de mémoire V
sur lequel opérer, l'ancienne valeur attendue A
et la nouvelle valeur B
. CAS
met à jour atomiquement V
à la nouvelle valeur B
, mais uniquement si la valeur dans V
correspond à l'ancienne valeur attendue A
; sinon ça ne fait rien. Dans les deux cas, il renvoie la valeur actuellement dans V
. Ceci est utilisé par la JVM dans AtomicInteger
, AtomicReference
et appelle la fonction compareAndSet()
. Si cette fonctionnalité n'est pas prise en charge par le processeur sous-jacent, JVM l'implémente par spin lock .
Comme indiqué, volatile
ne traite que de la visibilité.
Considérez cet extrait dans un environnement simultané:
boolean isStopped = false;
:
:
while (!isStopped) {
// do some kind of work
}
L'idée ici est que certains threads pourraient changer la valeur de isStopped
de false à true afin d'indiquer à la boucle suivante qu'il est temps d'arrêter de faire une boucle.
Intuitivement, il n'y a pas de problème. Logiquement, si un autre thread rend isStopped
égal à true, la boucle doit se terminer. La réalité est que la boucle ne se terminera probablement jamais, même si un autre thread rend isStopped
égal à true.
La raison à cela n’est pas intuitive, mais considérons que les processeurs modernes ont plusieurs cœurs et que chaque cœur a plusieurs registres et plusieurs niveaux de mémoire cache qui ne sont pas accessibles aux autres processeurs. En d'autres termes, les valeurs mises en cache dans la mémoire locale d'un processeur sont non visibles aux threads s'exécutant sur un processeur différent. C’est l’un des problèmes centraux de la simultanéité: la visibilité.
Le modèle de mémoire Java ne donne aucune garantie quant au moment où les modifications apportées à une variable dans un thread peuvent être visibles par d'autres threads. Afin de garantir que les mises à jour sont visibles dès leur création, vous devez effectuer une synchronisation.
Le mot clé volatile
est une forme de synchronisation faible. Bien que cela ne fasse rien pour l'exclusion mutuelle ou l'atomicité, il fournit une garantie que les modifications apportées à une variable dans un thread deviendront visibles pour les autres threads dès qu'elle sera faite. Etant donné que les lectures et écritures individuelles sur des variables qui ne sont pas 8 octets sont atomiques en Java, la déclaration de variables volatile
fournit un mécanisme simple pour fournir une visibilité dans les situations où il n'y a pas d'autres exigences en matière d'atomicité ou d'exclusion mutuelle.
Le mot clé volatile
est utilisé:
long
et double
. (Tous les autres accès primitifs sont déjà garantis atomiques!)Les classes Java.util.concurrent.atomic.*
sont, selon Java Docs :
Un petit ensemble de classes de classes prenant en charge la programmation thread-safe sans verrou sur des variables uniques. En substance, les classes de ce package étendent la notion de valeurs, champs et éléments de tableau instables à ceux qui fournissent également une opération de mise à jour conditionnelle atomique de la forme:
boolean compareAndSet(expectedValue, updateValue);
Les classes atomiques sont construites autour de la fonction atomique compareAndSet(...)
qui correspond à une instruction atomique de la CPU. Les classes atomiques introduisent l'ordre arrive-avant comme le font les variables volatile
. (à une exception près: weakCompareAndSet(...)
).
À partir de la documentation Java:
Lorsqu'un thread voit une mise à jour d'une variable atomique causée par un faibleCompareAndSet, il ne voit pas nécessairement les mises à jour d'autres variables qui se sont produites avant le faibleCompareAndSet.
A votre question:
Cela signifie-t-il que quiconque se verrouille dessus définira d'abord sa valeur? Et si entre temps, un autre thread monte et lit l'ancienne valeur alors que le premier thread changeait de valeur, le nouveau thread ne lira-t-il pas son ancienne valeur?
Vous ne verrouillez rien. Ce que vous décrivez est une situation de concurrence typique qui se produira éventuellement si les threads accèdent à des données partagées sans une synchronisation appropriée. Comme déjà mentionné, la déclaration d'une variable volatile
ne garantit dans ce cas que les autres threads verront le changement de la variable (la valeur ne sera pas mise en cache dans un registre de cache mis en évidence par un seul thread).
Quelle est la différence entre
AtomicInteger
etvolatile int
?
AtomicInteger
fournit des opérations atomiques sur un int
avec une synchronisation correcte (par exemple, incrementAndGet()
, getAndAdd(...)
, ...), volatile int
ne fera que garantir la visibilité de la int
à d'autres threads. .
Alors que se passera-t-il si deux threads attaquent une variable primitive volatile en même temps?
Habituellement, chacun peut augmenter la valeur. Cependant, parfois, les deux mettront à jour la valeur en même temps et au lieu d’augmenter le total de 2, les deux threads incrémenteront de 1 et seulement 1 sera ajouté.
Cela signifie-t-il que quiconque se verrouille dessus définira d'abord sa valeur?.
Il n'y a pas de verrou. C’est ce à quoi synchronized
est destiné.
Et si entre temps, un autre thread monte et lit l'ancienne valeur alors que le premier thread changeait de valeur, le nouveau thread ne lira-t-il pas son ancienne valeur?
Oui,
Quelle est la différence entre Atomic et le mot clé volatile?
AtomicXxxx encapsule les données volatiles de manière à les rendre identiques, à la différence qu'il fournit des opérations de niveau supérieur, telles que CompareAndSwap, utilisé pour implémenter l'incrément.
AtomicXxxx prend également en charge lazySet. Cela ressemble à un ensemble volatile, mais ne bloque pas le pipeline en attente de l'écriture. Cela peut signifier que si vous lisez une valeur que vous écrivez, vous pouvez voir l'ancienne valeur, mais vous ne devriez pas le faire de toute façon. La différence est que la définition d’une valeur volatile prend environ 5 ns et le bit lazySet prend environ 0,5 ns.