Volatile booléen vs AtomicBoolean

Que fait AtomicBoolean qu’un booléen volatile ne peut pas atteindre?

Ils sont tout à fait différents. Prenons l’exemple d’un entier volatile :

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

Si deux threads appellent la fonction simultanément, il se peut que 5 soit après, puisque le code compilé sera quelque peu similaire (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 access atomique à celle-ci est synchronisé, mais il n’est pas toujours évident de savoir ce qui constitue un access atomique. Avec un object Atomic* , il est garanti que chaque méthode est “atomique”.

Ainsi, si vous utilisez un AtomicInteger et un getAndAdd(int delta) , vous pouvez être sûr que le résultat sera 10 . De la même manière, si deux threads négocient tous deux une variable boolean même temps, avec AtomicBoolean vous pouvez être sûr qu’il a la valeur initiale après, avec un volatile boolean , vous ne pouvez pas.

Donc, chaque fois que vous avez plusieurs threads 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 en cours d’exécution loop() et un autre thread appelant stop() , vous pouvez rencontrer une boucle infinie si vous omettez volatile , car le premier thread peut mettre en cache la valeur de stop. Ici, le volatile sert de conseil au compilateur pour être un peu plus prudent avec les optimisations.

J’utilise des champs volatils lorsque ce champ est UNIQUEMENT MIS À JOUR par son thread propriétaire et que la valeur est uniquement lue par les autres threads, vous pouvez le considérer comme un scénario de publication / abonnement où il y a beaucoup d’observateurs mais un seul éditeur. Cependant, si ces observateurs doivent exécuter une logique basée sur la valeur du champ et repousser ensuite une nouvelle valeur, je vais avec des vars, des verrous ou des blocs synchronisés Atomic *, tout 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 (ils ont juste appris qu’ils sont sans verrou, ils ont donc un avantage sur les verrous ou les blocs synchronisés)

Vous ne pouvez pas faire compareAndSet , getAndSet tant getAndSet atomique avec un booléen volatile (sauf si vous le synchronisez bien sûr).

AtomicBoolean a des méthodes qui effectuent leurs opérations composites de manière atomique et sans avoir à utiliser un bloc synchronized . En revanche, volatile boolean ne peuvent effectuer des opérations composées que s’ils le font dans un bloc synchronized .

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

Par exemple, la méthode compareAndSet effectuera de manière atomique 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 ne sera exécuté qu’une seule fois, même s’il est appelé depuis plusieurs threads. Par exemple:

 final AtomicBoolean isJobDone = new AtomicBoolean(false); ... if (isJobDone.compareAndSet(false, true)) { listener.notifyJobDone(); } 

Est garanti de ne notifier le listener qu’une seule fois (en supposant qu’aucun autre thread ne remet le AtomicBoolean sur false après avoir été défini sur true ).

volatile mot-clé volatile 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’access à cette variable booléenne.

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 local.

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, le volatil garantit le principe «passe avant». Donc, si vous avez besoin d’un simple get () et set (), vous n’avez pas besoin de 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 “true, alors 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 un booléen volatile, vous aurez besoin d’une synchronisation pour vous assurer que la valeur n’a pas changé entre get et set.

Volatile booléen 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 définir un Atomic *, vous pourriez tout aussi bien avoir un champ volatile à la place.

Que fait AtomicBoolean qu’un booléen volatile ne peut pas atteindre?

Ce que les classes Atomic * vous donnent cependant, ce sont 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 locking. C’est pourquoi les classes Atomic * sont si puissantes.

Par exemple, si plusieurs threads utilisent le code suivant en utilisant ++ , il y aura des conditions de ++ car ++ est en fait: get, increment et set.

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

Toutefois, le code suivant fonctionnera dans un environnement multithread en toute sécurité:

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

Il est également important de noter que l’encapsulation de votre champ volatile à l’aide de la classe Atomic * est un bon moyen d’encapsuler la ressource partagée critique du sharepoint vue de l’object. Cela signifie que les développeurs ne peuvent pas simplement gérer le champ en supposant qu’il ne soit pas partagé en injectant éventuellement des problèmes avec un champ ++; ou autre code qui introduit des conditions de course.

Rappelez-vous l’IDIOM –

READ – MODIFY- WRITE ce que vous ne pouvez pas réaliser avec des

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

  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 risquez d’obtenir un résultat erroné. Méthodes AtomicBoolean éviter ce problème en effectuant les étapes #1 et #2 atomique.