Je comprends en quelque sorte que AtomicInteger et d'autres variables atomiques permettent des accès simultanés. Dans quels cas cette classe est-elle généralement utilisée?
la source
Je comprends en quelque sorte que AtomicInteger et d'autres variables atomiques permettent des accès simultanés. Dans quels cas cette classe est-elle généralement utilisée?
Il existe deux utilisations principales de AtomicInteger
:
En tant que compteur atomique ( incrementAndGet()
, etc.) pouvant être utilisé simultanément par de nombreux threads
En tant que primitive prenant en charge l' instruction compare-and-swap ( compareAndSet()
) pour implémenter des algorithmes non bloquants.
Voici un exemple de générateur de nombres aléatoires non bloquant de la concurrence Java en pratique de Brian Göetz :
public class AtomicPseudoRandom extends PseudoRandom {
private AtomicInteger seed;
AtomicPseudoRandom(int seed) {
this.seed = new AtomicInteger(seed);
}
public int nextInt(int n) {
while (true) {
int s = seed.get();
int nextSeed = calculateNext(s);
if (seed.compareAndSet(s, nextSeed)) {
int remainder = s % n;
return remainder > 0 ? remainder : remainder + n;
}
}
}
...
}
Comme vous pouvez le voir, cela fonctionne essentiellement de la même manière que incrementAndGet()
, mais effectue des calculs arbitraires ( calculateNext()
) au lieu d'incrémenter (et traite le résultat avant le retour).
read
etwrite that value + 1
, cela est détecté plutôt que d'écraser l'ancienne mise à jour (en évitant le problème de "perte de mise à jour"). Il s'agit en fait d'un cas spécial decompareAndSet
- si l'ancienne valeur était2
, la classe appelle en faitcompareAndSet(2, 3)
- donc si un autre thread a modifié la valeur entre-temps, la méthode d'incrémentation redémarre effectivement depuis le début.L'exemple le plus simple absolu auquel je puisse penser est de faire de l'incrémentation une opération atomique.
Avec des pouces standard:
Avec AtomicInteger:
Ce dernier est un moyen très simple d'effectuer des effets de mutations simples (notamment le comptage, ou l'indexation unique), sans avoir à recourir à la synchronisation de tous les accès.
Une logique sans synchronisation plus complexe peut être utilisée en utilisant
compareAndSet()
comme type de verrouillage optimiste - obtenir la valeur actuelle, calculer le résultat sur cette base, définir ce résultat si la valeur siff est toujours l'entrée utilisée pour effectuer le calcul, sinon recommencer - mais le les exemples de comptage sont très utiles, et je vais souvent utiliserAtomicIntegers
pour le comptage et les générateurs uniques à l'échelle de la machine virtuelle s'il y a un soupçon de plusieurs threads impliqués, car ils sont si faciles à travailler que je considérerais presque comme une optimisation prématurée d'utiliser plainints
.Bien que vous puissiez presque toujours obtenir les mêmes garanties de synchronisation
ints
et lessynchronized
déclarations appropriées , la beauté de la fonctionnalitéAtomicInteger
est que la sécurité des threads est intégrée à l'objet lui-même, plutôt que de vous soucier des entrelacements possibles et des moniteurs détenus de chaque méthode. cela arrive pour accéder à laint
valeur. Il est beaucoup plus difficile de violer accidentellement la sécurité des threads lors de l'appelgetAndIncrement()
que lors du retouri++
et de la mémorisation (ou non) pour acquérir au préalable le bon ensemble de moniteurs.la source
Si vous regardez les méthodes d'AtomicInteger, vous remarquerez qu'elles ont tendance à correspondre aux opérations courantes sur les entiers. Par exemple:
est la version thread-safe de ceci:
La carte des méthodes comme ceci:
++i
isi.incrementAndGet()
i++
isi.getAndIncrement()
--i
isi.decrementAndGet()
i--
isi.getAndDecrement()
i = x
isi.set(x)
x = i
is isx = i.get()
Il existe également d'autres méthodes pratiques, comme
compareAndSet
ouaddAndGet
la source
L'utilisation principale de
AtomicInteger
est lorsque vous êtes dans un contexte multithread et que vous devez effectuer des opérations thread-safe sur un entier sans utilisersynchronized
. L'affectation et la récupération sur le type primitifint
sont déjà atomiques maisAtomicInteger
s'accompagnent de nombreuses opérations qui ne sont pas atomiquesint
.Les plus simples sont les
getAndXXX
orxXXAndGet
. Par exemple,getAndIncrement()
est un équivalent atomiquei++
qui n'est pas atomique car il s'agit en fait d'un raccourci pour trois opérations: récupération, addition et affectation.compareAndSet
est très utile pour implémenter des sémaphores, des verrous, des verrous, etc.L'utilisation de
AtomicInteger
est plus rapide et plus lisible que l'exécution de la même chose en utilisant la synchronisation.Un test simple:
Sur mon PC avec Java 1.6, le test atomique s'exécute en 3 secondes tandis que celui synchronisé s'exécute en 5,5 secondes environ. Le problème ici est que l'opération de synchronisation (
notAtomic++
) est vraiment courte. Le coût de la synchronisation est donc très important par rapport à l'opération.A côté de l'atomicité, AtomicInteger peut être utilisé comme une version mutable de
Integer
par exemple dansMap
s comme valeurs.la source
AtomicInteger
comme clé de carte, car elle utilise l'equals()
implémentation par défaut , ce qui n'est certainement pas ce que vous attendez de la sémantique si elle est utilisée dans une carte.Par exemple, j'ai une bibliothèque qui génère des instances d'une classe. Chacune de ces instances doit avoir un ID entier unique, car ces instances représentent des commandes envoyées à un serveur, et chaque commande doit avoir un ID unique. Étant donné que plusieurs threads sont autorisés à envoyer des commandes simultanément, j'utilise un AtomicInteger pour générer ces ID. Une approche alternative serait d'utiliser une sorte de verrou et un entier régulier, mais c'est à la fois plus lent et moins élégant.
la source
Comme l'a dit gabuzo, parfois j'utilise AtomicIntegers quand je veux passer un int par référence. C'est une classe intégrée qui a du code spécifique à l'architecture, c'est donc plus facile et probablement plus optimisé que n'importe quel MutableInteger que je pourrais rapidement coder. Cela dit, cela ressemble à un abus de la classe.
la source
En Java 8, les classes atomiques ont été étendues avec deux fonctions intéressantes:
Les deux utilisent la fonction updateFunction pour effectuer la mise à jour de la valeur atomique. La différence est que la première renvoie l'ancienne valeur et la seconde renvoie la nouvelle valeur. La fonction updateFunction peut être implémentée pour effectuer des opérations de «comparaison et de définition» plus complexes que les opérations standard. Par exemple, il peut vérifier que le compteur atomique ne descend pas en dessous de zéro, il nécessiterait normalement une synchronisation, et ici le code est sans verrou:
Le code est tiré de Java Atomic Example .
la source
J'utilise généralement AtomicInteger lorsque j'ai besoin de donner des ID à des objets qui peuvent être accédés ou créés à partir de plusieurs threads, et je l'utilise généralement comme attribut statique sur la classe à laquelle j'accède dans le constructeur des objets.
la source
Vous pouvez implémenter des verrous non bloquants à l'aide de compareAndSwap (CAS) sur des entiers atomiques ou des longs. Le papier "Tl2" Software Transactional Memory décrit ceci:
Ce qu'il décrit, c'est d'abord lire l'entier atomique. Divisez ceci en un bit de verrouillage ignoré et le numéro de version. Tentative d'écrire CAS en tant que bit de verrouillage effacé avec le numéro de version actuel dans l'ensemble de bits de verrouillage et le numéro de version suivant. Boucle jusqu'à ce que vous réussissiez et que vous soyez le thread qui possède le verrou. Déverrouillez en définissant le numéro de version actuel avec le bit de verrouillage effacé. Le document décrit l'utilisation des numéros de version dans les verrous pour coordonner que les threads ont un ensemble cohérent de lectures lorsqu'ils écrivent.
Cet article décrit que les processeurs prennent en charge le matériel pour les opérations de comparaison et d'échange, ce qui les rend très efficaces. Il affirme également:
la source
La clé est qu'ils permettent un accès simultané et une modification en toute sécurité. Ils sont couramment utilisés comme compteurs dans un environnement multithread - avant leur introduction, cela devait être une classe écrite par l'utilisateur qui regroupait les différentes méthodes dans des blocs synchronisés.
la source
J'ai utilisé AtomicInteger pour résoudre le problème du Dining Philosopher.
Dans ma solution, des instances AtomicInteger ont été utilisées pour représenter les fourches, il y en a deux par philosophe. Chaque philosophe est identifié comme un entier, de 1 à 5. Lorsqu'une fourchette est utilisée par un philosophe, l'AtomicInteger détient la valeur du philosophe, 1 à 5, sinon la fourchette n'est pas utilisée, la valeur de l'AtomicInteger est donc -1 .
L'AtomicInteger permet ensuite de vérifier si une fourchette est libre, valeur == - 1, et de la définir sur le propriétaire de la fourche si elle est libre, en une seule opération atomique. Voir le code ci-dessous.
Étant donné que la méthode compareAndSet ne bloque pas, elle devrait augmenter le débit, plus de travail est effectué. Comme vous le savez peut-être, le problème des philosophes de la restauration est utilisé lorsqu'un accès contrôlé aux ressources est nécessaire, c'est-à-dire des fourches, comme un processus a besoin de ressources pour continuer à travailler.
la source
Exemple simple pour la fonction compareAndSet ():
L'imprimé est: valeur précédente: 0 La valeur a été mise à jour et c'est 6 Un autre exemple simple:
L'imprimé est: Valeur précédente: 0 La valeur n'a pas été mise à jour
la source