Quand utilisons-nous AtomicReference
?
Est-il nécessaire de créer des objets dans tous les programmes multithreads ?
Donnez 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 multithreads ?
Donnez un exemple simple où AtomicReference doit être utilisé.
La référence atomique doit être utilisée dans un cadre où vous devez faire des opérations simples. atomique (c'est-à-dire Sécurité des fils (non triviales) sur une référence, pour lesquelles la synchronisation basée sur les moniteurs n'est pas appropriée. Supposons que vous vouliez définir un champ spécifique uniquement si l'état de l'objet a changé pendant le traitement :
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 la référence atomique, vous pouvez faire cela même si le fichier cache
est partagé entre les threads, sans utiliser synchronized
. En général, il est préférable d'utiliser des synchroniseurs ou la fonction java.util.concurrent
plutôt qu'un simple cadre Atomic*
à moins que tu ne saches ce que tu fais.
Deux excellentes références en matière d'arbres morts qui vous permettront de vous familiariser avec ce sujet :
Notez que (je ne sais pas si cela a toujours été vrai) référence (c'est-à-dire =
) est lui-même atomique (mise à jour primitif Les types 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 s'il est en 64 bits) sans utiliser explicitement une fonction Atomic*
.
Voir le Spécification du langage Java 3ed, Section 17.7 .
Corrigez-moi si je me trompe, mais il semble que la clé de la nécessité de cette opération est que vous devez faire un "compareAndSet". Si tout ce que j'avais besoin de faire était set, je n'aurais pas du tout besoin de l'AtomicObject parce que les mises à jour des références sont elles-mêmes atomiques ?
Est-il sûr de faire cache.compareAndSet(cachedValue, someFunctionOfOld(cachedValueToUpdate)) ? C'est-à-dire en mettant le calcul en ligne ?
@veggen Les arguments de fonction en Java sont évalués avant la fonction elle-même, donc l'inlining ne fait aucune différence dans ce cas. Oui, c'est sûr.
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 très dense, je vais donc la décomposer un peu.
Premièrement, un objet immuable est un objet qui n'est effectivement pas modifié après sa construction. Souvent, les méthodes d'un objet immuable renvoient de nouvelles instances de cette même classe. Parmi les exemples, citons les classes enveloppes Long
y Double
ainsi que String
pour n'en citer que quelques-uns. (Selon Programmer la concussion sur la JVM les objets immuables sont un élément essentiel de la concurrence moderne).
Ensuite, pourquoi un AtomicReference
mieux qu'un volatile
pour partager cette valeur commune ? Un simple exemple de code 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 voulez modifier la chaîne de caractères 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'arriver pendant ce temps et de modifier la valeur au milieu de la nouvelle concaténation de la chaîne. Ensuite, lorsque votre thread reprend, vous bloquez le travail de l'autre thread. Mais honnêtement, ce code fonctionnera, il a l'air propre, et il rendra la plupart des gens heureux.
Un léger problème. C'est lent. Surtout s'il y a beaucoup de contention de cet objet verrou. C'est parce que la plupart des verrous nécessitent un appel système de l'OS, et votre thread va se bloquer et être changé de contexte hors du CPU pour faire place à d'autres processus.
L'autre option consiste à utiliser une 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() + "let's add something";
// Compare and set
success = shared.compareAndSet(prevValue, newValue);
}
Pourquoi est-ce mieux ? Honnêtement, ce code est un peu moins propre qu'avant. Mais il y a quelque chose de vraiment important qui se passe sous le capot dans AtomicRefrence, et c'est la comparaison et l'échange. C'est une seule instruction du CPU, et non un appel du système d'exploitation, qui permet de réaliser la permutation. C'est une seule instruction sur le CPU. Et comme il n'y a pas de verrou, il n'y a pas de changement de contexte dans le cas où le verrou est exercé, ce qui permet de gagner encore plus de temps !
Le hic, c'est que pour les AtomicReferences, cela n'utilise pas de .equals()
mais plutôt un ==
comparaison pour la valeur attendue. Assurez-vous donc que la valeur attendue est l'objet réel renvoyé par get dans la boucle.
Vos deux exemples se comportent différemment. Il faudrait que vous boucliez sur worked
pour obtenir la même sémantique.
Je pense que vous devriez initialiser la valeur à l'intérieur du constructeur AtomicReference, sinon un autre thread peut encore voir la valeur nulle avant que vous appeliez shared.set. (A moins que shared.set soit exécuté dans un initialisateur statique).
Dans votre deuxième exemple, vous devriez, à partir de Java 8, utiliser quelque chose comme : shared.updateAndGet( (x) -> (x+"lets add something")) ; ... qui appellera de manière répétée le .compareAndSet jusqu'à ce que cela fonctionne. C'est équivalent au bloc synchronisé qui réussirait toujours. Vous devez cependant vous assurer que la lambda que vous passez est sans effet secondaire, car elle peut être appelée plusieurs fois.
Voici un cas d'utilisation de AtomicReference :
Considérez cette classe qui agit comme une plage de nombres et utilise des variables AtmomicInteger individuelles pour maintenir les limites inférieures et supérieures des nombres.
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 toutes deux des séquences check-then-act, mais elles n'utilisent pas suffisamment de verrouillage pour les rendre atomiques. Si la plage de nombres contient (0, 10), et qu'un thread appelle setLower(5) alors 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 contient maintenant (5, 4), un état invalide. Ainsi, alors que les AtomicIntegers sous-jacents sont thread-safe, la classe composite ne l'est pas. Ce problème peut être résolu en utilisant une AtomicReference au lieu d'utiliser des AtomicIntegers individuels pour les limites supérieure et inférieure.
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;
}
}
}
Cet article est similaire à votre réponse, mais va plus en profondeur vers des choses plus compliquées. C'est intéressant ! ibm.com/developerworks/java/library/j-jtp04186
Bonjour ! Ce lien est cassé, pouvez-vous trouver le lien disponible de cet article ? @LppEdd
Vous pouvez utiliser AtomicReference lors de l'application de verrous optimistes. Vous avez un objet partagé et vous voulez le modifier à partir de plus d'un thread.
Comme d'autres fils de discussion ont pu le modifier et peuvent le faire entre ces deux étapes. Vous devez le faire dans une opération atomique. C'est là que AtomicReference peut vous aider.
Quand utilisons-nous AtomicReference ?
Référence atomique est un moyen flexible de mettre à jour la valeur de la variable de manière atomique sans utiliser la synchronisation.
AtomicReference
supportent la programmation thread-safe sans verrou sur des variables uniques.
Il existe de multiples façons d'assurer la sécurité du fil avec un niveau élevé de sécurité. concurrentes API. Les variables atomiques sont l'une des multiples options.
Lock
supportent des idiomes de verrouillage qui simplifient de nombreuses applications concurrentes.
Executors
définir une API de haut niveau pour le lancement et la gestion des threads. Les implémentations d'exécuteurs fournies par java.util.concurrent permettent de gérer des pools de threads adaptés aux applications à grande échelle.
Collections simultanées facilitent la gestion de grandes collections de données et peuvent réduire considérablement le besoin de synchronisation.
Variables atomiques ont des caractéristiques qui minimisent la synchronisation et permettent d'éviter les erreurs de cohérence de la mémoire.
Donnez 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 multithreads ?
Vous n'avez pas besoin d'utiliser AtomicReference
dans tous les programmes multi-filières.
Si vous voulez garder une seule variable, utilisez AtomicReference
. Si vous voulez protéger un bloc de code, utilisez d'autres constructions telles que Lock
/ synchronized
etc.
Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.