Praktische Anwendungen für AtomicInteger - Java, Concurrency, Atomic

Ich verstehe, dass AtomicInteger und andere atomare Variablen gleichzeitige Zugriffe erlauben. In welchen Fällen wird diese Klasse normalerweise verwendet?

Antworten:

154 für die Antwort № 1

Es gibt zwei Hauptverwendungen von AtomicInteger:

  • Als Atomzähler (incrementAndGet()usw.), die von vielen Threads gleichzeitig verwendet werden können

  • Als ein primitives, das unterstützt Vergleichen und Tauschen Anweisung (compareAndSet()) um nicht blockierende Algorithmen zu implementieren.

    Hier ist ein Beispiel für nicht blockierende Zufallszahlengenerator aus Brian Götz's Java Concurrency in der Praxis:

    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 Prinzip fast genauso wie incrementAndGet(), führt aber eine beliebige Berechnung durch (calculateNext()) anstelle von increment (und verarbeitet das Ergebnis vor der Rückgabe).


83 für die Antwort № 2

Das absolut einfachste Beispiel, das ich mir vorstellen kann, besteht darin, eine atomare Operation zu inkrementieren.

Mit Standard-Ints:

private volatile int counter;

public int getNextUniqueIndex() {
return counter++; // Not atomic, multiple threads could get the same result
}

Mit AtomicInteger:

private AtomicInteger counter;

public int getNextUniqueIndex() {
return counter.getAndIncrement();
}

Letzteres ist eine sehr einfache Möglichkeit, einfache Mutationseffekte (insbesondere das Zählen oder einmalige Indizieren) durchzuführen, ohne auf den gesamten Zugriff zu synchronisieren.

Komplexere Synchronisation-freie Logik kann durch Verwendung verwendet werden compareAndSet() als eine Art optimistisches Locking - erhalte den aktuellen Wert, berechne das Ergebnis basierend darauf, setze dieses Ergebnis wenn Der Wert ist immer noch die Eingabe, die für die Berechnung verwendet wird, sonst starte neu - aber die Zählbeispiele sind sehr nützlich, und ich werde oft verwenden AtomicIntegers für das Zählen und VM-weit einzigartige Generatoren, wenn es einen Hinweis auf mehrere Threads gibt, weil sie so einfach zu arbeiten sind, ich halte es fast für vorzeitige Optimierung, um einfach zu verwenden ints.

Dabei können Sie fast immer die gleichen Synchronisationsgarantien mit erreichen ints und angemessen synchronized Erklärungen, die Schönheit von AtomicInteger ist, dass die Thread-Sicherheit in das eigentliche Objekt selbst eingebaut ist, anstatt dass man sich über die möglichen Interleavings und Monitore, die gehalten werden müssen, jeder Methode, auf die der Zugriff erfolgt, Sorgen machen muss int Wert. Es ist viel schwieriger versehentlich die threadsafety beim Aufruf zu verletzen getAndIncrement() als bei der Rückkehr i++ und sich daran zu erinnern (oder nicht), vorher den richtigen Satz von Monitoren zu erhalten.


51 für die Antwort № 3

Wenn Sie sich die Methoden von AtomicInteger ansehen, werden Sie feststellen, dass sie den üblichen Operationen auf Ints entsprechen.

static AtomicInteger i;

// Later, in a thread
int current = i.incrementAndGet();

ist die Thread-sichere Version von diesem:

static int i;

// Later, in a thread
int current = ++i;

Die Methoden ordnen sich wie folgt an:
++i ist i.incrementAndGet()
i++ ist i.getAndIncrement()
--i ist i.decrementAndGet()
i-- ist i.getAndDecrement()
i = x ist i.set(x)
x = i ist x = i.get()

Es gibt auch andere Bequemlichkeitsmethoden, wie compareAndSet oder addAndGet


31 für die Antwort № 4

Die primäre Verwendung von AtomicInteger Wenn Sie sich in einem Multithread-Kontext befinden, müssen Sie threadsichere Operationen für eine Ganzzahl ausführen, ohne sie zu verwenden synchronized. Die Zuweisung und Abfrage auf dem primitiven Typ int sind schon atomare aber AtomicInteger kommt mit vielen Operationen, die nicht atomar sind int.

Die einfachsten sind die getAndXXX oder xXXAndGet. Zum Beispiel getAndIncrement() ist ein Atomäquivalent zu i++ Das ist nicht atomar, weil es eigentlich eine Abkürzung für drei Operationen ist: Abruf, Addition und Zuweisung. compareAndSet ist sehr nützlich, um Semaphore, Schlösser, Verriegelungen usw. zu implementieren.

Verwendung der AtomicInteger ist schneller und besser lesbar als die gleiche Synchronisation.

Ein einfacher Test:

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));
}

Auf meinem PC mit Java 1.6 läuft der atomare Test in 3 Sekunden, während der synchronisierte in ungefähr 5,5 Sekunden läuft. Das Problem hier ist, dass die Operation zu synchronisieren (notAtomic++) ist wirklich kurz. Daher sind die Kosten der Synchronisation im Vergleich zur Operation sehr wichtig.

Neben der Atomarität kann AtomicInteger als eine veränderbare Version von verwendet werden Integer zum Beispiel in Maps als Werte.


13 für die Antwort № 5

Zum Beispiel habe ich eine Bibliothek, die erzeugtInstanzen einer Klasse. 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 einen AtomicInteger, um diese IDs zu generieren. Ein alternativer Ansatz wäre die Verwendung einer Art von Sperre und einer regulären Ganzzahl, aber das ist sowohl langsamer als auch weniger elegant.


6 für die Antwort № 6

In Java wurden 8 atomare Klassen um zwei interessante Funktionen erweitert:

  • int getAndUpdate (IntUnaryOperator updateFunction)
  • int updateAndGet (IntUnaryOperator updateFunction)

Beide verwenden die updateFunction zum AusführenAktualisierung des atomaren Wertes. Der Unterschied besteht darin, dass der erste Wert den alten Wert zurückgibt und der zweite den neuen Wert zurückgibt. Die Funktion updateFunction kann implementiert werden, um komplexere Operationen zum Vergleichen und Einstellen als die Standardoperation auszuführen. Zum Beispiel kann es überprüfen, dass der Atomzähler nicht unter Null geht, normalerweise würde es eine Synchronisation erfordern, und hier ist der Code frei von Sperren:

    public class Counter {

private final AtomicInteger number;

public Counter(int number) {
this.number = new AtomicInteger(number);
}

/** @return true if still can decrease */
public boolean dec() {
// updateAndGet(fn) executed atomically:
return number.updateAndGet(n -> (n > 0) ? n - 1 : n) > 0;
}
}

Der Code stammt aus Java Atomic Beispiel.


5 für die Antwort № 7

Wie Gabuzo sagte, verwende ich manchmal AtomicIntegerswenn ich einen int durch Verweis übergeben möchte. Es ist eine integrierte Klasse, die architekturspezifischen Code hat, also ist es einfacher und wahrscheinlich optimierter als jeder MutableInteger, den ich schnell kodieren könnte. Das heißt, es fühlt sich an wie ein Missbrauch der Klasse.


4 für die Antwort № 8

Normalerweise verwende ich AtomicInteger, wenn ich etwas geben mussIdentifiziert Objekte, auf die aus mehreren Threads zugegriffen werden kann, und ich verwende sie normalerweise als statisches Attribut für die Klasse, auf die ich im Konstruktor der Objekte zugreife.


3 für die Antwort № 9

Sie können nicht blockierende Sperren mit compareAndSwap (CAS) für ganze Zahlen oder Longs implementieren. Das "Tl2" Software Transaktionsspeicher Papier beschreibt dies:

Wir assoziieren eine spezielle versionierte Schreibsperre mitjeder hat gehandelt Speicherort. In seiner einfachsten Form ist die versionierte Schreibsperre ein Einzelwort Spinlock, das eine CAS-Operation verwendet, um die Sperre zu erhalten und ein Geschäft, um es zu veröffentlichen. Da braucht man nur ein einziges Bit anzugeben dass die Sperre genommen wird, verwenden wir den Rest des Sperrworts, um a zu halten Versionsnummer.

Was es beschreibt, ist zuerst das atomare lesenganze Zahl. Teilen Sie dies in ein ignoriertes Lock-Bit und die Versionsnummer auf. Versuch, CAS als das mit der aktuellen Versionsnummer gelöschte Lock-Bit in den Lock-Bit-Satz und die nächste Versionsnummer zu schreiben. Schleife, bis du erfolgreich bist, und dein Thread ist derjenige, der das Schloss besitzt. Entsperren, indem Sie die aktuelle Versionsnummer mit deaktiviertem Sperr-Bit setzen. Der Artikel beschreibt die Verwendung der Versionsnummern in den Sperren, um zu koordinieren, dass Threads beim Schreiben eine konsistente Menge von Lesevorgängen aufweisen.

Dieser Artikel beschreibt, dass Prozessoren Hardwareunterstützung für Vergleichs- und Tauschoperationen haben, die sehr effizient sind. Es behauptet auch:

Nicht blockierende CAS-basierte Zähler, die atomare Variablen verwenden, sind besser Leistung als Lock-basierte Zähler in geringer bis mittlerer Konkurrenz


1 für die Antwort № 10

Der Schlüssel ist, dass sie den gleichzeitigen Zugriff erlauben undÄnderung sicher. 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 umschloss.


Speisekarte