Que fait AtomicBoolean qu'un booléen volatil ne peut pas réaliser?
Que fait AtomicBoolean qu'un booléen volatil ne peut pas réaliser?
Réponses:
Ils sont juste totalement différents. Considérez cet exemple d'un volatile
entier:
volatile int i = 0;
void incIBy5() {
i += 5;
}
Si deux threads appellent la fonction simultanément, i
peut être 5 par la suite, car le code compilé sera quelque peu similaire à ceci (sauf que vous ne pouvez pas synchroniser dessus int
):
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Si une variable est volatile, chaque accès atomique à celle-ci est synchronisé, mais il n'est pas toujours évident de savoir ce qui est réellement considéré comme un accès atomique. Avec un Atomic*
objet, il est garanti que chaque méthode est "atomique".
Ainsi, si vous utilisez un AtomicInteger
et getAndAdd(int delta)
, vous pouvez être sûr que le résultat sera 10
. De la même manière, si deux threads annulent boolean
simultanément une variable, avec un AtomicBoolean
vous pouvez être sûr qu'elle a la valeur d'origine par la suite, avec un volatile boolean
, vous ne pouvez pas.
Ainsi, chaque fois que vous avez plusieurs threads modifiant un champ, vous devez le rendre atomique ou utiliser une synchronisation explicite.
Le but de volatile
est différent. Considérez cet exemple
volatile boolean stop = false;
void loop() {
while (!stop) { ... }
}
void stop() { stop = true; }
Si vous avez un thread en cours d'exécution loop()
et un autre thread appelant stop()
, vous pouvez exécuter une boucle infinie si vous omettez volatile
, car le premier thread peut mettre en cache la valeur d'arrêt. Ici, le volatile
sert d'indicateur au compilateur pour être un peu plus prudent avec les optimisations.
volatile
. La question est sur le point volatile boolean
vs AtomicBoolean
.
volatile boolean
suffit. S'il y a aussi de nombreux écrivains, vous en aurez peut-être besoin AtomicBoolean
.
J'utilise des champs volatils lorsque ce champ est UNIQUEMENT MIS À JOUR par son thread propriétaire et que la valeur n'est lue que par d'autres threads, vous pouvez le considérer comme un scénario de publication / abonnement où il y a de nombreux observateurs mais un seul éditeur. Cependant, si ces observateurs doivent exécuter une logique basée sur la valeur du champ, puis repousser une nouvelle valeur, je choisis des vars ou des verrous Atomic * ou des blocs synchronisés, selon ce qui me convient le mieux. Dans de nombreux scénarios simultanés, il se résume à obtenir la valeur, à la comparer à une autre et à la mettre à jour si nécessaire, d'où les méthodes compareAndSet et getAndSet présentes dans les classes Atomic *.
Consultez les JavaDocs du package java.util.concurrent.atomic pour obtenir une liste des classes Atomic et une excellente explication de leur fonctionnement (nous venons d'apprendre qu'elles sont sans verrouillage, afin qu'elles aient un avantage sur les verrous ou les blocs synchronisés)
boolean
var, nous devons choisir volatile boolean
.
Vous ne pouvez pas le faire compareAndSet
, getAndSet
comme une opération atomique avec un booléen volatil (sauf si vous le synchronisez bien sûr).
AtomicBoolean
a des méthodes qui effectuent leurs opérations composées de manière atomique et sans avoir à utiliser un synchronized
bloc. D'un autre côté, volatile boolean
ne peut effectuer des opérations composées que si cela se fait dans un synchronized
bloc.
Les effets de mémoire de la lecture / écriture sur volatile boolean
sont identiques à get
et aux set
méthodes de AtomicBoolean
respectivement.
Par exemple, la compareAndSet
méthode exécutera atomiquement ce qui suit (sans synchronized
bloc):
if (value == expectedValue) {
value = newValue;
return true;
} else {
return false;
}
Par conséquent, la compareAndSet
méthode vous permettra d'écrire du code dont l'exécution est garantie une seule fois, même lorsqu'elle est appelée à partir de plusieurs threads. Par exemple:
final AtomicBoolean isJobDone = new AtomicBoolean(false);
...
if (isJobDone.compareAndSet(false, true)) {
listener.notifyJobDone();
}
Est garanti de ne notifier l'auditeur qu'une seule fois (en supposant qu'aucun autre thread ne remette le AtomicBoolean
dos false
après qu'il ait été défini sur true
).
volatile
Le mot clé garantit une relation entre les threads partageant cette variable. Cela ne vous garantit pas que 2 threads ou plus ne s'interrompent pas lors de l'accès à cette variable booléenne.
Atomic*
classe encapsule un volatile
champ.
Volatile booléen vs AtomicBoolean
Les classes Atomic * encapsulent une primitive volatile du même type. De la source:
public class AtomicLong extends Number implements java.io.Serializable {
...
private volatile long value;
...
public final long get() {
return value;
}
...
public final void set(long newValue) {
value = newValue;
}
Donc, si tout ce que vous faites est d'obtenir et de définir un Atomic *, vous pourriez tout aussi bien avoir un champ volatil à la place.
Que fait AtomicBoolean qu'un booléen volatil ne peut pas réaliser?
Les classes Atomic * vous offrent des méthodes qui fournissent des fonctionnalités plus avancées telles que incrementAndGet()
, compareAndSet()
et d'autres qui implémentent plusieurs opérations (get / increment / set, test / set) sans verrouillage. C'est pourquoi les classes Atomic * sont si puissantes.
Par exemple, si plusieurs threads utilisent le code suivant à l'aide de ++
, il y aura des conditions de concurrence critique en raison de ++
: get, increment et set.
private volatile value;
...
// race conditions here
value++;
Cependant, le code suivant fonctionnera en toute sécurité dans un environnement multi-thread sans verrous:
private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();
Il est également important de noter que l'habillage de votre champ volatile à l'aide de la classe Atomic * est un bon moyen d'encapsuler la ressource partagée critique du point de vue d'un objet. Cela signifie que les développeurs ne peuvent pas simplement traiter le champ en supposant qu'il n'est pas partagé, ce qui peut entraîner des problèmes avec un champ ++; ou tout autre code introduisant des conditions de course.
S'il existe plusieurs threads accédant à la variable de niveau classe, chaque thread peut conserver une copie de cette variable dans son cache threadlocal.
Rendre la variable volatile empêchera les threads de conserver la copie de la variable dans le cache threadlocal.
Les variables atomiques sont différentes et permettent une modification atomique de leurs valeurs.
Le type primitif booléen est atomique pour les opérations d'écriture et de lecture, volatile garantit le principe passe-avant. Donc, si vous avez besoin d'un simple get () et set (), vous n'avez pas besoin de l'AtomicBoolean.
D'un autre côté, si vous devez implémenter une vérification avant de définir la valeur d'une variable, par exemple, "si vrai puis défini sur faux", vous devez également effectuer cette opération de manière atomique, dans ce cas, utilisez compareAndSet et d'autres méthodes fournies par AtomicBoolean, car si vous essayez d'implémenter cette logique avec un booléen volatile, vous aurez besoin d'une synchronisation pour vous assurer que la valeur n'a pas changé entre get et set.
Rappelez-vous l'IDIOM -
LIRE - MODIFIER - ÉCRIRE ce que vous ne pouvez pas réaliser avec volatile
volatile
ne fonctionne que dans les cas où le thread propriétaire a la possibilité de mettre à jour la valeur du champ et les autres threads ne peuvent que lire.
Si vous n'avez qu'un seul thread modifiant votre booléen, vous pouvez utiliser un booléen volatile (vous le faites généralement pour définir une stop
variable vérifiée dans la boucle principale du thread).
Cependant, si vous avez plusieurs threads modifiant le booléen, vous devez utiliser un AtomicBoolean
. Sinon, le code suivant n'est pas sûr:
boolean r = !myVolatileBoolean;
Cette opération se fait en deux étapes:
Si un autre thread modifie la valeur entre #1
et 2#
, vous pourriez obtenir un résultat incorrect. AtomicBoolean
Les méthodes évitent ce problème en faisant des étapes #1
et de manière #2
atomique.
Les deux sont du même concept, mais en booléen atomique, il fournira l'atomicité de l'opération au cas où le commutateur CPU se produirait entre les deux.