Je comprends en quelque sorte que AtomicInteger et d'autres variables Atomic permettent des accès simultanés. Dans quels cas cette classe est-elle généralement utilisée?
Réponses
Trop de publicités?Il existe deux principaux usages de l' AtomicInteger
:
Atomique contre (
incrementAndGet()
, etc) qui peuvent être utilisées par plusieurs threads simultanément-
Comme un primitif qui prend en charge compare-and-swap instruction (
compareAndSet()
) à mettre en œuvre non-blocage des algorithmes.Voici un exemple de non-blocage du générateur de nombres aléatoires à partir de Brian Göetz Java de la Simultanéité Dans la Pratique:
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, il fonctionne presque de la même manière que
incrementAndGet()
,, mais effectue arbitraire de calcul (calculateNext()
) au lieu d'incrémenter (processus et le résultat avant de revenir).
L'absolu exemple le plus simple je pense est de faire incrémentation d'une opération atomique.
Avec la norme ints:
private volatile int counter;
public int getNextUniqueIndex() {
return counter++; // Not atomic, multiple threads could get the same result
}
Avec AtomicInteger:
private AtomicInteger counter;
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
Ce dernier est un moyen très simple pour effectuer de simples mutations effets (en particulier le comptage ou unique d'indexation), sans avoir à recourir à la synchronisation de tous les accès.
Plus complexes de synchronisation sans logique peuvent être employées, en utilisant compareAndSet()
comme un type de verrouillage optimiste - obtenir la valeur actuelle, de calculer, de résultat sur cette base, de définir ce résultat iff valeur est toujours l'entrée utilisée pour faire le calcul, sinon recommencer - mais le comptage des exemples sont très utiles, et je vais souvent utiliser AtomicIntegers
pour le comptage et le VM-unique générateurs si il n'y a aucun soupçon de plusieurs threads d'être impliqués parce qu'ils sont si facile de travailler avec, je l'avais presque considérer qu'il est prématuré d'optimisation de l'utilisation de la plaine ints
.
Alors que vous pouvez presque toujours en mesure de réaliser la même synchronisation des garanties ints
et échéant synchronized
des déclarations, la beauté de l' AtomicInteger
, c'est que le fil de sécurité est intégré dans l'objet réel lui-même, plutôt que de vous avoir à vous soucier des entrelacements, et surveille les détenus, de chaque méthode qui se passe pour accéder à l' int
de la valeur. Il est beaucoup plus difficile accidentellement violer threadsafety lors de l'appel d' getAndIncrement()
que lors du retour de la i++
et de se souvenir (ou pas) pour acquérir la bonne série de moniteurs à l'avance.
Si vous observez les méthodes de AtomicInteger a, vous remarquerez qu'ils ont tendance à correspondre à la commune des opérations sur les entiers. Par exemple:
static AtomicInteger i;
// Later, in a thread
int current = i.incrementAndGet();
est la version thread-safe:
static int i;
// Later, in a thread
int current = ++i;
Les méthodes de la carte comme ceci:++i
est i.incrementAndGet()
i++
est i.getAndIncrement()
--i
est i.decrementAndGet()
i--
est i.getAndDecrement()
i = x
est i.set(x)
x = i
est x = i.get()
Il y a d'autres méthodes bien pratiques, comme compareAndSet
ou addAndGet
L'usage principal de l' AtomicInteger
, c'est quand vous êtes dans un contexte multithread et vous avez besoin pour effectuer fil sécurité des opérations sur un nombre entier sans l'aide d' synchronized
. L'assignation et la récupération sur le type primitif int
sont déjà atomique, mais AtomicInteger
est livré avec de nombreuses opérations qui ne sont pas atomiques sur int
.
Les plus simples sont l' getAndXXX
ou xXXAndGet
. Par exemple, getAndIncrement()
est atomique est équivalent à i++
qui n'est pas atomique, car il est en fait un raccourci pour trois opérations: extraction, de l'addition et de la cession. compareAndSet
est très utile pour implémente les sémaphores, les serrures, loquets, etc.
À l'aide de l' AtomicInteger
est plus rapide et plus lisible que d'effectuer la même à l'aide de la synchronisation.
Un test simple:
public synchronized int incrementNotAtomic() {
return notAtomic++;
}
public void performTestNotAtomic() {
final long start = System.currentTimeMillis();
for (int i = 0 ; i < NUM ; i++) {
incrementNotAtomic();
}
System.out.println("Not atomic: "+(System.currentTimeMillis() - start));
}
public void performTestAtomic() {
final long start = System.currentTimeMillis();
for (int i = 0 ; i < NUM ; i++) {
atomic.getAndIncrement();
}
System.out.println("Atomic: "+(System.currentTimeMillis() - start));
}
Sur mon PC avec la version 1.6 de Java le test atomique s'exécute en 3 secondes, tandis que la synchronisation fonctionne dans environ 5,5 secondes. Le problème ici est que l'opération de synchronisation (notAtomic++
) est vraiment court. De sorte que le coût de la synchronisation est vraiment important par rapport à l'opération.
À côté de l'atomicité AtomicInteger peut être utiliser comme un mutable version de Integer
par exemple, en Map
s comme des valeurs.
Par exemple, j'ai une bibliothèque qui génère des instances de certaines classes. 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 consisterait à utiliser une sorte de verrou et un nombre entier régulier, mais c'est à la fois plus lent et moins élégant.