Ich verstehe irgendwie, dass AtomicInteger und andere Atomic-Variablen gleichzeitige Zugriffe ermöglichen. In welchen Fällen wird diese Klasse normalerweise verwendet?
quelle
Ich verstehe irgendwie, dass AtomicInteger und andere Atomic-Variablen gleichzeitige Zugriffe ermöglichen. In welchen Fällen wird diese Klasse normalerweise verwendet?
Es gibt zwei Hauptverwendungen von AtomicInteger
:
Als Atomzähler ( incrementAndGet()
usw.), der von vielen Threads gleichzeitig verwendet werden kann
Als Grundelement, das den Befehl zum Vergleichen und Austauschen ( compareAndSet()
) unterstützt, um nicht blockierende Algorithmen zu implementieren.
Hier ist ein Beispiel für einen nicht blockierenden Zufallszahlengenerator aus Brian Göetz 'Java Concurrency In Practice :
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;
}
}
}
...
}
Wie Sie sehen können, funktioniert es im Grunde fast genauso wie incrementAndGet()
, führt jedoch eine willkürliche Berechnung ( calculateNext()
) anstelle eines Inkrements durch (und verarbeitet das Ergebnis vor der Rückgabe).
read
und ändertwrite that value + 1
, dies erkannt wird, anstatt das alte Update zu überschreiben (um das Problem "verlorenes Update" zu vermeiden). Dies ist tatsächlich ein Sonderfall voncompareAndSet
- wenn der alte Wert war2
, ruft die Klasse tatsächlich aufcompareAndSet(2, 3)
- wenn also ein anderer Thread den Wert in der Zwischenzeit geändert hat, wird die Inkrementierungsmethode effektiv von Anfang an neu gestartet.Das absolut einfachste Beispiel, das ich mir vorstellen kann, ist das Inkrementieren einer atomaren Operation.
Mit Standard-Ints:
Mit AtomicInteger:
Letzteres ist eine sehr einfache Möglichkeit, einfache Mutationseffekte (insbesondere Zählen oder eindeutige Indizierung) durchzuführen, ohne den gesamten Zugriff synchronisieren zu müssen.
Eine komplexere synchronisationsfreie Logik kann verwendet werden, indem
compareAndSet()
eine Art optimistisches Sperren verwendet wird. Ermitteln Sie den aktuellen Wert, berechnen Sie das Ergebnis basierend darauf, setzen Sie dieses Ergebnis, wenn der Wert immer noch die Eingabe für die Berechnung ist, andernfalls starten Sie erneut Zählbeispiele sind sehr nützlich, und ich verwende sie häufigAtomicIntegers
zum Zählen und für VM-weite eindeutige Generatoren, wenn es Hinweise darauf gibt, dass mehrere Threads beteiligt sind, da sie so einfach zu bearbeiten sind. Ich würde es fast als vorzeitige Optimierung betrachten, einfach zu verwendenints
.Während Sie fast immer die gleichen Synchronisationsgarantien mit
ints
und entsprechendesynchronized
Deklarationen erzielen können , besteht das Schöne daran,AtomicInteger
dass die Thread-Sicherheit in das eigentliche Objekt selbst eingebaut ist, anstatt sich um die möglichen Verschachtelungen und Monitore jeder Methode kümmern zu müssen das passiert, um auf denint
Wert zuzugreifen . Es ist viel schwieriger, beim Anrufen versehentlich die Thread-Sicherheit zu verletzen,getAndIncrement()
als wenn Sie zurückkehreni++
und sich daran erinnern (oder nicht), vorher die richtigen Monitore zu erwerben.quelle
Wenn Sie sich die Methoden von AtomicInteger ansehen, werden Sie feststellen, dass sie in der Regel allgemeinen Operationen für Ints entsprechen. Zum Beispiel:
ist die thread-sichere Version davon:
Die Methodenzuordnung
++i
lautet wie folgt : isi.incrementAndGet()
i++
isi.getAndIncrement()
--i
isi.decrementAndGet()
i--
isi.getAndDecrement()
i = x
isi.set(x)
x = i
is isx = i.get()
Es gibt auch andere Bequemlichkeitsmethoden wie
compareAndSet
oderaddAndGet
quelle
Die Hauptverwendung von
AtomicInteger
ist, wenn Sie sich in einem Multithread-Kontext befinden und threadsichere Operationen für eine Ganzzahl ohne Verwendung ausführen müssensynchronized
. Die Zuweisung und das Abrufen des primitiven Typsint
sind bereits atomar, es gibt jedochAtomicInteger
viele Operationen, die nicht atomar sindint
.Am einfachsten sind die
getAndXXX
oderxXXAndGet
. Zum BeispielgetAndIncrement()
ist ein atomares Äquivalent,i++
das nicht atomar ist, weil es tatsächlich eine Abkürzung für drei Operationen ist: Abrufen, Hinzufügen und Zuweisen.compareAndSet
ist sehr nützlich, um Semaphoren, Schlösser, Latches usw. zu implementieren.Die Verwendung von
AtomicInteger
ist schneller und besser lesbar als die Verwendung derselben mithilfe der Synchronisierung.Ein einfacher Test:
Auf meinem PC mit Java 1.6 läuft der Atomtest in 3 Sekunden, während der synchronisierte in etwa 5,5 Sekunden läuft. Das Problem hierbei ist, dass die Operation zum Synchronisieren (
notAtomic++
) sehr kurz ist. Die Kosten für die Synchronisation sind also im Vergleich zum Betrieb sehr wichtig.Neben der Atomizität kann AtomicInteger als veränderbare Version von
Integer
beispielsweise inMap
s als Werte verwendet werden.quelle
AtomicInteger
als Kartenschlüssel verwenden möchte , da es die Standardimplementierung verwendetequals()
, die mit ziemlicher Sicherheit nicht der Semantik entspricht, die in einer Karte verwendet wird.Zum Beispiel habe ich eine Bibliothek, die Instanzen einer Klasse generiert. Jede dieser Instanzen muss eine eindeutige Ganzzahl-ID haben, da diese Instanzen Befehle darstellen, die an einen Server gesendet werden, und jeder Befehl muss eine eindeutige ID haben. Da mehrere Threads gleichzeitig Befehle senden dürfen, verwende ich eine AtomicInteger, um diese IDs zu generieren. Ein alternativer Ansatz wäre die Verwendung einer Art Sperre und einer regulären Ganzzahl, aber das ist sowohl langsamer als auch weniger elegant.
quelle
Wie Gabuzo sagte, verwende ich manchmal AtomicIntegers, wenn ich ein Int als Referenz übergeben möchte. Es ist eine integrierte Klasse mit architekturspezifischem Code, daher ist sie einfacher und wahrscheinlich optimierter als jede MutableInteger, die ich schnell codieren könnte. Das heißt, es fühlt sich wie ein Missbrauch der Klasse an.
quelle
In Java 8 wurden Atomklassen um zwei interessante Funktionen erweitert:
Beide verwenden die updateFunction, um die Aktualisierung des Atomwerts durchzuführen. Der Unterschied besteht darin, dass der erste den alten Wert und der zweite den neuen Wert zurückgibt. Die updateFunction kann implementiert werden, um komplexere "Vergleichs- und Set" -Operationen als die Standardoperation auszuführen. Zum Beispiel kann überprüft werden, ob der Atomzähler nicht unter Null fällt, normalerweise würde eine Synchronisation erforderlich sein, und hier ist der Code sperrenfrei:
Der Code stammt aus Java Atomic Example .
quelle
Normalerweise verwende ich AtomicInteger, wenn ich Objekten IDs geben muss, auf die zugegriffen oder aus mehreren Threads erstellt werden kann, und ich verwende es normalerweise als statisches Attribut für die Klasse, auf die ich im Konstruktor der Objekte zugreife.
quelle
Sie können nicht blockierende Sperren mit compareAndSwap (CAS) für atomare Ganzzahlen oder Longs implementieren. Das Dokument "Tl2" -Software-Transaktionsspeicher beschreibt dies:
Was es beschreibt, ist zuerst die atomare Ganzzahl zu lesen. Teilen Sie dies in ein ignoriertes Sperrbit und die Versionsnummer auf. Versuchen Sie, CAS als das mit der aktuellen Versionsnummer gelöschte Sperrbit in das gesperrte Sperrbit und die nächste Versionsnummer zu schreiben. Schleife, bis du erfolgreich bist und du der Thread bist, dem die Sperre gehört. Entsperren Sie, indem Sie die aktuelle Versionsnummer bei deaktiviertem Sperrbit einstellen. In diesem Artikel wird die Verwendung der Versionsnummern in den Sperren beschrieben, um zu koordinieren, dass Threads beim Schreiben einen konsistenten Satz von Lesevorgängen aufweisen.
In diesem Artikel wird beschrieben, dass Prozessoren Hardware-Unterstützung für Vergleichs- und Auslagerungsvorgänge bieten, wodurch dies sehr effizient ist. Es behauptet auch:
quelle
Der Schlüssel ist, dass sie den gleichzeitigen Zugriff und die Änderung sicher ermöglichen. Sie werden häufig als Zähler in einer Multithread-Umgebung verwendet. Vor ihrer Einführung musste dies eine vom Benutzer geschriebene Klasse sein, die die verschiedenen Methoden in synchronisierten Blöcken zusammenfasste.
quelle
Ich habe AtomicInteger verwendet, um das Problem des Dining Philosopher zu lösen.
In meiner Lösung wurden AtomicInteger-Instanzen verwendet, um die Gabeln darzustellen. Pro Philosoph werden zwei benötigt. Jeder Philosoph wird als Ganzzahl 1 bis 5 identifiziert. Wenn eine Gabel von einem Philosophen verwendet wird, enthält die AtomicInteger den Wert des Philosophen 1 bis 5, andernfalls wird die Gabel nicht verwendet, sodass der Wert der AtomicInteger -1 beträgt .
Die AtomicInteger ermöglicht es dann, in einer atomaren Operation zu überprüfen, ob eine Gabel frei ist, Wert == - 1, und sie auf den Besitzer der Gabel zu setzen, falls sie frei ist. Siehe Code unten.
Da die compareAndSet-Methode nicht blockiert, sollte sie den Durchsatz erhöhen und mehr Arbeit leisten. Wie Sie vielleicht wissen, wird das Problem der Dining Philosophen verwendet, wenn ein kontrollierter Zugriff auf Ressourcen erforderlich ist, dh Gabeln, wie ein Prozess Ressourcen benötigt, um die Arbeit fortzusetzen.
quelle
Einfaches Beispiel für die Funktion compareAndSet ():
Der gedruckte Wert lautet: Vorheriger Wert: 0 Der Wert wurde aktualisiert und ist 6 Ein weiteres einfaches Beispiel:
Der Ausdruck lautet: Vorheriger Wert: 0 Der Wert wurde nicht aktualisiert
quelle