Que fait AtomicBoolean que boolean volatile ne peut pas réaliser?
Réponses
Trop de publicités?J'utilise des champs volatils lorsque ledit champ est UNIQUEMENT MIS À JOUR par son thread propriétaire et que la valeur est seulement lue 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 effectuer une certaine logique basée sur la valeur du champ et ensuite renvoyer une nouvelle valeur, alors j'utilise des variables Atomic* ou des verrous ou des blocs synchronisés, selon ce qui me convient le mieux. Dans de nombreux scénarios concurrents, cela revient à 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 la JavaDocs du package java.util.concurrent.atomic pour une liste des classes Atomic et une excellente explication de leur fonctionnement (je viens de découvrir qu'elles sont sans verrou, donc elles ont un avantage sur les verrous ou les blocs synchronisés)
Ils sont tout à fait différents. Considérez cet exemple pour un entier volatile
:
volatile int i = 0;
void incIBy5() {
i += 5;
}
Si deux threads appellent la fonction simultanément, i
pourrait être égal à 5 par la suite, puisque le code compilé sera quelque peu similaire à ceci (à l'exception que vous ne pouvez pas synchroniser sur un int
) :
void incIBy5() {
int temp;
synchronisé(i) { temp = i }
synchronisé(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 déterminer ce qui qualifie réellement un accès atomique. Avec un objet Atomic*
, 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 négativent simultanément une variable boolean
, avec un AtomicBoolean
, vous pouvez être sûr qu'elle aura la valeur d'origine par la suite, avec un volatile boolean
, non.
Ainsi chaque fois que vous avez plus d'un thread modifiant un champ, vous devez le rendre atomique ou utiliser une synchronisation explicite.
Le but du 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 exécutant loop()
et un autre thread appelant stop()
, vous pourriez rencontrer une boucle infinie si vous omettez volatile
, puisque le premier thread pourrait mettre en cache la valeur de stop. Ici, le volatile
sert d'indice au compilateur pour être un peu plus prudent avec les optimisations.
AtomicBoolean
possède des méthodes qui exécutent leurs opérations combinées de manière atomique et sans avoir besoin d'utiliser un bloc synchronized
. D'autre part, volatile boolean
ne peut exécuter des opérations combinées que s'il est fait à l'intérieur d'un bloc synchronized
.
Les effets de mémoire de la lecture/écriture de volatile boolean
sont identiques aux méthodes get
et set
de AtomicBoolean
respectivement.
Par exemple, la méthode compareAndSet
exécutera atomiquement ce qui suit (sans bloc synchronized
) :
if (value == expectedValue) {
value = newValue;
return true;
} else {
return false;
}
Par conséquent, la méthode compareAndSet
vous permettra d'écrire du code qui est garanti de s'exécuter une seule fois, même lorsqu'il est appelé par plusieurs threads. Par exemple :
final AtomicBoolean isJobDone = new AtomicBoolean(false);
...
if (isJobDone.compareAndSet(false, true)) {
listener.notifyJobDone();
}
Est garanti de ne notifier le auditeur qu'une seule fois (en supposant qu'aucun autre thread ne réinitialise l'AtomicBoolean
à false
après l'avoir défini sur true
).