web-dev-qa-db-fra.com

Booléen Volatile vs AtomicBoolean

Que fait AtomicBoolean qu’un booléen volatil ne peut pas réaliser?

214
JeffV

Ils sont juste totalement différents. Prenons cet exemple d'un entier volatile:

volatile int i = 0;
void incIBy5() {
    i += 5;
}

Si deux threads appellent la fonction simultanément, i pourrait être 5 après, car le code compilé ressemblera à celui-ci (sauf que vous ne pouvez pas synchroniser sur int):

void incIBy5() {
    int temp;
    synchronized(i) { temp = i }
    synchronized(i) { i = temp + 5 }
}

Si une variable est volatile, chaque accès atomique est synchronisé, mais il n’est pas toujours évident de déterminer ce qui peut être qualifié d’accès atomique. Avec un objet Atomic*, il est garanti que chaque méthode est "atomique".

Ainsi, si vous utilisez 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 nient 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, vous ne pourrez pas.

Donc chaque fois que vous avez plusieurs threads en train de modifier 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 exécutant loop() et un autre thread appelant stop(), vous pouvez vous heurter à une boucle infinie si vous omettez volatile, car le premier thread pourrait mettre la valeur en cache d'arrêter. Ici, le volatile indique au compilateur d’être un peu plus prudent avec les optimisations.

86
Cephalopod

J'utilise des champs volatiles lorsque ledit 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 dans lequel il y a de nombreux observateurs mais un seul éditeur. Cependant, si ces observateurs doivent effectuer une certaine logique en fonction de la valeur du champ puis repousser une nouvelle valeur, alors je vais avec Atomic *, verrous ou blocs synchronisés, ce qui me convient le mieux. Dans de nombreux scénarios simultanés, il s'agit d'obtenir la valeur, de la comparer à une autre et de 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 (elles viennent d'apprendre qu'elles sont sans verrou, elles ont donc un avantage sur serrures ou blocs synchronisés)

244
teto

Vous ne pouvez pas utiliser compareAndSet, getAndSet comme opération atomique avec un booléen volatil (à moins bien sûr que vous ne le synchronisiez).

51
nanda

AtomicBoolean a des méthodes qui effectuent leurs opérations composées de manière atomique et sans avoir à utiliser un bloc synchronized. D'autre part, volatile boolean ne peut effectuer des opérations composées que si cela est fait dans un bloc synchronized.

Les effets de mémoire de la lecture/écriture à volatile boolean sont identiques aux méthodes get et set de AtomicBoolean respectivement.

Par exemple, la méthode compareAndSet effectuera atomiquement les opérations suivantes (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 dont l'exécution est garantie, une seule fois, même si 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 redéfinit le AtomicBoolean sur false après qu’il a été défini sur true).

40
Nam San

volatile mot-clé garantit que se passe avant la relation entre les threads partageant cette variable. Cela ne vous garantit pas que 2 threads ou plus ne s'interrompent pas lorsqu'ils accèdent à cette variable booléenne.

14
dhblah

S'il existe plusieurs threads accédant à une variable de niveau classe, chaque thread peut conserver une copie de cette variable dans son cache de threadlocal.

Rendre la variable volatile empêchera les threads de conserver la copie de variable dans le cache threadlocal.

Les variables atomiques sont différentes et permettent la modification atomique de leurs valeurs.

5
Amol Gaikwad

Booléen Volatile vs AtomicBoolean

Les classes Atomic * enveloppent 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 configurer un Atomic *, vous pouvez aussi bien avoir un champ volatile à la place.

Que fait AtomicBoolean qu’un booléen volatil ne peut pas réaliser?

Les classes Atomic * vous fournissent des méthodes offrant des fonctionnalités plus avancées telles que incrementAndGet(), compareAndSet() et d'autres qui implémentent plusieurs opérations (obtenir/incrémenter/définir, tester/définir) 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 car ++ Est en réalité: get, increment et set.

private volatile value;
...
// race conditions here
value++;

Toutefois, le code suivant fonctionnera dans un environnement multi-thread en toute sécurité, sans verrou:

private final AtomicLong value = new AtomicLong();
...
value.incrementAndGet();

Il est également important de noter que le fait d'encapsuler votre champ volatile à l'aide de la classe Atomic * constitue 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 tout simplement pas traiter le champ en supposant qu'il ne soit pas partagé, ce qui risque d'injecter des problèmes avec un champ ++; ou un autre code introduisant des conditions de concurrence.

5
Gray

Le type primitif booléen est atomique pour les opérations d'écriture et de lecture, volatile garantit le principe d'événement précédent. Donc, si vous avez besoin de simples get () et set (), vous n'avez pas besoin de AtomicBoolean.

Par contre, si vous devez implémenter une vérification avant de définir la valeur d’une variable, par exemple. "si true, alors défini sur false", 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 des valeurs booléennes volatiles, vous aurez besoin d'une certaine synchronisation pour assurez-vous que la valeur n'a pas changé entre get et set.

4
user2660000

Rappelez-vous l'IDIOM -

LIRE - MODIFIER - ÉCRIRE ce que tu ne peux pas réaliser avec volatile

3
MoveFast

Si vous n'avez qu'un seul thread modifiant votre booléen, vous pouvez utiliser un booléen volatil (vous le faites généralement pour définir une variable stop coché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:

  1. La valeur booléenne est lue.
  2. La valeur booléenne est écrite.

Si un autre thread modifie la valeur entre #1 et 2#, vous pourriez avoir un mauvais résultat. Les méthodes AtomicBoolean évitent ce problème en effectuant les étapes #1 et #2 atomiquement.

2
Thibaut D.