Quand utilisons-nous AtomicReference
?
Est-il nécessaire de créer des objets dans tous les programmes multithread?
Fournissez un exemple simple où AtomicReference doit être utilisé.
Quand utilisons-nous AtomicReference
?
Est-il nécessaire de créer des objets dans tous les programmes multithread?
Fournissez un exemple simple où AtomicReference doit être utilisé.
Réponses:
La référence atomique doit être utilisée dans un paramètre où vous devez effectuer des opérations atomiques simples (c'est -à- dire thread-safe , non triviales) sur une référence, pour lesquelles la synchronisation basée sur moniteur n'est pas appropriée. Supposons que vous souhaitiez vérifier si un champ spécifique uniquement si l'état de l'objet reste le même que lors de votre dernière vérification:
AtomicReference<Object> cache = new AtomicReference<Object>();
Object cachedValue = new Object();
cache.set(cachedValue);
//... time passes ...
Object cachedValueToUpdate = cache.get();
//... do some work to transform cachedValueToUpdate into a new version
Object newValue = someFunctionOfOld(cachedValueToUpdate);
boolean success = cache.compareAndSet(cachedValue,cachedValueToUpdate);
En raison de la sémantique de référence atomique, vous pouvez le faire même si l' cache
objet est partagé entre les threads, sans utiliser synchronized
. En général, il vaut mieux utiliser des synchroniseurs ou le java.util.concurrent
framework plutôt que bare Atomic*
sauf si vous savez ce que vous faites.
Deux excellentes références d'arbre mort qui vous présenteront ce sujet:
Notez que (je ne sais pas si cela a toujours été vrai) l' affectation de référence (c'est-à-dire =
) est elle-même atomique (la mise à jour des types primitifs 64 bits comme long
ou double
peut ne pas être atomique; mais la mise à jour d'une référence est toujours atomique, même si elle est 64 bits ) sans utiliser explicitement un Atomic*
.
Voir la spécification Java Language 3ed, Section 17.7 .
AtomicReference
vous devez marquer la variable volatile
parce que tandis que le runtime garantit que l'attribution de référence est atomique, le compilateur peut effectuer des optimisations en supposant que la variable n'a pas été modifiée par d'autres threads.
AtomicReference
»; si vous êtes l' utilisez, mon conseil serait d'aller dans la direction opposée et le marquer de final
sorte que le compilateur peut optimiser en conséquence.
Une référence atomique est idéale à utiliser lorsque vous devez partager et modifier l'état d'un objet immuable entre plusieurs threads. C'est une déclaration super dense donc je vais la décomposer un peu.
Premièrement, un objet immuable est un objet qui n'est effectivement pas modifié après la construction. Souvent, les méthodes d'un objet immuable renvoient de nouvelles instances de cette même classe. Quelques exemples incluent les classes wrapper de Long et Double, ainsi que String, pour n'en nommer que quelques-unes. (Selon la programmation simultanée sur la JVM, les objets immuables sont un élément essentiel de la concurrence moderne).
Ensuite, pourquoi AtomicReference est meilleur qu'un objet volatil pour partager cette valeur partagée. Un exemple de code simple montrera la différence.
volatile String sharedValue;
static final Object lock=new Object();
void modifyString(){
synchronized(lock){
sharedValue=sharedValue+"something to add";
}
}
Chaque fois que vous souhaitez modifier la chaîne référencée par ce champ volatile en fonction de sa valeur actuelle, vous devez d'abord obtenir un verrou sur cet objet. Cela empêche un autre thread d'entrer pendant ce temps et de modifier la valeur au milieu de la nouvelle concaténation de chaînes. Ensuite, lorsque votre thread reprend, vous encombrez le travail de l'autre thread. Mais honnêtement, ce code fonctionnera, il a l'air propre et il rendrait la plupart des gens heureux.
Léger problème. C'est lent. Surtout s'il y a beaucoup de conflits avec cet objet verrou. C'est parce que la plupart des verrous nécessitent un appel système OS, et votre thread se bloquera et sera contextualisé hors du CPU pour faire place à d'autres processus.
L'autre option consiste à utiliser un AtomicRefrence.
public static AtomicReference<String> shared = new AtomicReference<>();
String init="Inital Value";
shared.set(init);
//now we will modify that value
boolean success=false;
while(!success){
String prevValue=shared.get();
// do all the work you need to
String newValue=shared.get()+"lets add something";
// Compare and set
success=shared.compareAndSet(prevValue,newValue);
}
Maintenant, pourquoi est-ce mieux? Honnêtement, ce code est un peu moins propre qu'auparavant. Mais il y a quelque chose de vraiment important qui se passe sous le capot dans AtomicRefrence, à savoir comparer et échanger. C'est une seule instruction CPU, et non un appel OS, qui fait que le changement se produit. Il s'agit d'une seule instruction sur le CPU. Et comme il n'y a pas de verrous, il n'y a pas de changement de contexte dans le cas où le verrou s'exerce ce qui fait gagner encore plus de temps!
La capture est, pour AtomicReferences, cela n'utilise pas un appel .equals (), mais plutôt une comparaison == pour la valeur attendue. Assurez-vous donc que l'attendu est l'objet réel renvoyé par get in the loop.
worked
pour obtenir la même sémantique.
Voici un cas d'utilisation pour AtomicReference:
Considérez cette classe qui agit comme une plage de nombres et utilise des variables AtmomicInteger individuelles pour maintenir les bornes numériques inférieures et supérieures.
public class NumberRange {
// INVARIANT: lower <= upper
private final AtomicInteger lower = new AtomicInteger(0);
private final AtomicInteger upper = new AtomicInteger(0);
public void setLower(int i) {
// Warning -- unsafe check-then-act
if (i > upper.get())
throw new IllegalArgumentException(
"can't set lower to " + i + " > upper");
lower.set(i);
}
public void setUpper(int i) {
// Warning -- unsafe check-then-act
if (i < lower.get())
throw new IllegalArgumentException(
"can't set upper to " + i + " < lower");
upper.set(i);
}
public boolean isInRange(int i) {
return (i >= lower.get() && i <= upper.get());
}
}
SetLower et setUpper sont des séquences de vérification puis d'action, mais elles n'utilisent pas un verrouillage suffisant pour les rendre atomiques. Si la plage de numéros est maintenue (0, 10) et qu'un thread appelle setLower (5) tandis qu'un autre thread appelle setUpper (4), avec un timing malchanceux, les deux passeront les vérifications dans les setters et les deux modifications seront appliquées. Le résultat est que la plage détient désormais (5, 4) un état non valide. Ainsi, alors que les AtomicIntegers sous-jacents sont thread-safe, la classe composite ne l'est pas. Cela peut être résolu en utilisant un AtomicReference au lieu d'utiliser des AtomicIntegers individuels pour les limites supérieures et inférieures.
public class CasNumberRange {
// Immutable
private static class IntPair {
final int lower; // Invariant: lower <= upper
final int upper;
private IntPair(int lower, int upper) {
this.lower = lower;
this.upper = upper;
}
}
private final AtomicReference<IntPair> values =
new AtomicReference<IntPair>(new IntPair(0, 0));
public int getLower() {
return values.get().lower;
}
public void setLower(int lower) {
while (true) {
IntPair oldv = values.get();
if (lower > oldv.upper)
throw new IllegalArgumentException(
"Can't set lower to " + lower + " > upper");
IntPair newv = new IntPair(lower, oldv.upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
public int getUpper() {
return values.get().upper;
}
public void setUpper(int upper) {
while (true) {
IntPair oldv = values.get();
if (upper < oldv.lower)
throw new IllegalArgumentException(
"Can't set upper to " + upper + " < lower");
IntPair newv = new IntPair(oldv.lower, upper);
if (values.compareAndSet(oldv, newv))
return;
}
}
}
Vous pouvez utiliser AtomicReference lors de l'application de verrous optimistes. Vous avez un objet partagé et vous souhaitez le modifier à partir de plusieurs threads.
Comme un autre thread peut l'avoir modifié et / peut modifier entre ces 2 étapes. Vous devez le faire dans une opération atomique. c'est là qu'AtomicReference peut vous aider
Voici un cas d'utilisation très simple et n'a rien à voir avec la sécurité des threads.
Pour partager un objet entre des invocations lambda, AtomicReference
est une option :
public void doSomethingUsingLambdas() {
AtomicReference<YourObject> yourObjectRef = new AtomicReference<>();
soSomethingThatTakesALambda(() -> {
yourObjectRef.set(youObject);
});
soSomethingElseThatTakesALambda(() -> {
YourObject yourObject = yourObjectRef.get();
});
}
Je ne dis pas que c'est une bonne conception ou quoi que ce soit (c'est juste un exemple trivial), mais si vous avez le cas où vous devez partager un objet entre les invocations lambda, AtomicReference
c'est une option.
En fait, vous pouvez utiliser n'importe quel objet qui contient une référence, même une collection qui n'a qu'un seul élément. Cependant, l'AtomicReference est un ajustement parfait.
Je ne parlerai pas beaucoup. Mes amis amis respectés ont déjà donné leur précieuse contribution. Le code de course complet à la fin de ce blog devrait éliminer toute confusion. Il s'agit d'un petit programme de réservation de place de cinéma dans un scénario multi-thread.
Certains faits élémentaires importants sont les suivants. 1> Différents threads ne peuvent s'affronter que par exemple et les variables de membre statique dans l'espace de tas. 2> La lecture ou l'écriture volatile est complètement atomique et sérialisée / se produit avant et uniquement à partir de la mémoire. En disant cela, je veux dire que toute lecture suivra l'écriture précédente en mémoire. Et toute écriture suivra la lecture précédente de la mémoire. Ainsi, tout thread fonctionnant avec un volatile verra toujours la valeur la plus à jour. AtomicReference utilise cette propriété de volatile.
Voici une partie du code source d'AtomicReference. AtomicReference fait référence à une référence d'objet. Cette référence est une variable membre volatile dans l'instance AtomicReference comme ci-dessous.
private volatile V value;
get () renvoie simplement la dernière valeur de la variable (comme le font les volatiles de manière "antérieure").
public final V get()
Voici la méthode la plus importante d'AtomicReference.
public final boolean compareAndSet(V expect, V update) {
return unsafe.compareAndSwapObject(this, valueOffset, expect, update);
}
La méthode compareAndSet (expect, update) appelle la méthode compareAndSwapObject () de la classe dangereuse de Java. Cet appel de méthode de unsafe appelle l'appel natif, qui appelle une seule instruction au processeur. "attendre" et "mettre à jour" référencent chacun un objet.
Si et seulement si la variable de membre d'instance AtomicReference "valeur" fait référence au même objet est référencée par "expect", "update" est assigné à cette variable d'instance maintenant, et "true" est retourné. Sinon, false est retourné. Le tout se fait atomiquement. Aucun autre thread ne peut intercepter entre les deux. Comme il s'agit d'une opération à processeur unique (magie de l'architecture informatique moderne), elle est souvent plus rapide que l'utilisation d'un bloc synchronisé. Mais rappelez-vous que lorsque plusieurs variables doivent être mises à jour de manière atomique, AtomicReference n'aidera pas.
Je voudrais ajouter un code de course à part entière, qui peut être exécuté en éclipse. Cela dissiperait beaucoup de confusion. Ici, 22 utilisateurs (fils MyTh) tentent de réserver 20 sièges. Voici l'extrait de code suivi du code complet.
Extrait de code dans lequel 22 utilisateurs tentent de réserver 20 sièges.
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
Voici le code complet en cours d'exécution.
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ThreadLocalRandom;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
public class Solution {
static List<AtomicReference<Integer>> seats;// Movie seats numbered as per
// list index
public static void main(String[] args) throws InterruptedException {
// TODO Auto-generated method stub
seats = new ArrayList<>();
for (int i = 0; i < 20; i++) {// 20 seats
seats.add(new AtomicReference<Integer>());
}
Thread[] ths = new Thread[22];// 22 users
for (int i = 0; i < ths.length; i++) {
ths[i] = new MyTh(seats, i);
ths[i].start();
}
for (Thread t : ths) {
t.join();
}
for (AtomicReference<Integer> seat : seats) {
System.out.print(" " + seat.get());
}
}
/**
* id is the id of the user
*
* @author sankbane
*
*/
static class MyTh extends Thread {// each thread is a user
static AtomicInteger full = new AtomicInteger(0);
List<AtomicReference<Integer>> l;//seats
int id;//id of the users
int seats;
public MyTh(List<AtomicReference<Integer>> list, int userId) {
l = list;
this.id = userId;
seats = list.size();
}
@Override
public void run() {
boolean reserved = false;
try {
while (!reserved && full.get() < seats) {
Thread.sleep(50);
int r = ThreadLocalRandom.current().nextInt(0, seats);// excludes
// seats
//
AtomicReference<Integer> el = l.get(r);
reserved = el.compareAndSet(null, id);// null means no user
// has reserved this
// seat
if (reserved)
full.getAndIncrement();
}
if (!reserved && full.get() == seats)
System.out.println("user " + id + " did not get a seat");
} catch (InterruptedException ie) {
// log it
}
}
}
}
Quand utilisons-nous AtomicReference?
AtomicReference est un moyen flexible de mettre à jour la valeur de la variable atomiquement sans utiliser de synchronisation.
AtomicReference
Prend en charge la programmation sans thread et sans verrouillage sur des variables uniques.
Il existe plusieurs façons d'assurer la sécurité des threads avec une API simultanée de haut niveau . Les variables atomiques sont l'une des multiples options.
Lock
Les objets prennent en charge les idiomes de verrouillage qui simplifient de nombreuses applications simultanées.
Executors
définir une API de haut niveau pour le lancement et la gestion des threads. Les implémentations d'exécuteur fournies par java.util.concurrent fournissent une gestion de pool de threads adaptée aux applications à grande échelle.
Les collections simultanées facilitent la gestion de grandes collections de données et peuvent réduire considérablement le besoin de synchronisation.
Les variables atomiques ont des fonctionnalités qui minimisent la synchronisation et aident à éviter les erreurs de cohérence de la mémoire.
Fournissez un exemple simple où AtomicReference doit être utilisé.
Exemple de code avec AtomicReference
:
String initialReference = "value 1";
AtomicReference<String> someRef =
new AtomicReference<String>(initialReference);
String newReference = "value 2";
boolean exchanged = someRef.compareAndSet(initialReference, newReference);
System.out.println("exchanged: " + exchanged);
Est-il nécessaire de créer des objets dans tous les programmes multithread?
Vous n'êtes pas obligé de l'utiliser AtomicReference
dans tous les programmes multithread.
Si vous souhaitez protéger une seule variable, utilisez AtomicReference
. Si vous souhaitez protéger un bloc de code, utilisez d'autres constructions comme Lock
/ synchronized
etc.
Un autre exemple simple consiste à effectuer une modification de thread sécurisé dans un objet de session.
public PlayerScore getHighScore() {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
return holder.get();
}
public void updateHighScore(PlayerScore newScore) {
ServletContext ctx = getServletConfig().getServletContext();
AtomicReference<PlayerScore> holder
= (AtomicReference<PlayerScore>) ctx.getAttribute("highScore");
while (true) {
HighScore old = holder.get();
if (old.score >= newScore.score)
break;
else if (holder.compareAndSet(old, newScore))
break;
}
}
Source: http://www.ibm.com/developerworks/library/j-jtp09238/index.html