it-swarm.com.de

Praktische Verwendung für AtomicInteger

Ich verstehe, dass AtomicInteger und andere Atomic-Variablen gleichzeitige Zugriffe zulassen. In welchen Fällen wird diese Klasse jedoch normalerweise verwendet?

195
James P.

Es gibt zwei Hauptanwendungen von AtomicInteger:

  • Als atomarer Zähler (incrementAndGet() usw.), der von vielen Threads gleichzeitig verwendet werden kann

  • Als Grundelement, das compare-and-swap (compareAndSet()) unterstützt, um nicht blockierende Algorithmen zu implementieren 

    Hier ein Beispiel für einen nicht blockierenden Zufallszahlengenerator aus Brian Göetzs 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, funktioniert es im Prinzip fast genauso wie incrementAndGet(), führt jedoch willkürliche Berechnungen (calculateNext()) statt inkrementieren (und verarbeitet das Ergebnis vor der Rückkehr).

168
axtavt

Das absolut einfachste Beispiel, das ich mir vorstellen kann, ist das Inkrementieren einer atomaren Operation.

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 Methode, um einfache Mutationseffekte (insbesondere das Zählen oder die eindeutige Indizierung) auszuführen, ohne den gesamten Zugriff synchronisieren zu müssen.

Eine komplexere, synchronisationsfreie Logik kann verwendet werden, indem compareAndSet() als eine Art optimistisches Sperren verwendet wird - den aktuellen Wert abrufen, das Ergebnis berechnen und dieses Ergebnis festlegen iff value ist immer noch die zur Berechnung verwendete Eingabe, andernfalls Beginnen Sie erneut - aber die Zählbeispiele sind sehr nützlich, und ich verwende häufig AtomicIntegers zum Zählen und VM-weit eindeutige Generatoren, wenn es Anzeichen für mehrere Threads gibt, weil sie so einfach zu handhaben sind, würde ich fast darüber nachdenken Es ist eine vorzeitige Optimierung für die Verwendung von ints.

Während Sie mit ints und entsprechenden synchronized-Deklarationen fast immer die gleichen Synchronisationsgarantien erreichen können, ist AtomicInteger das Schöne, dass die Thread-Sicherheit in das eigentliche Objekt selbst integriert ist, anstatt sich um mögliche Verschachtelungen und Monitore kümmern zu müssen. von jeder Methode, die auf den int-Wert zugreift. Es ist viel schwieriger, bei einem Aufruf von getAndIncrement() versehentlich die Threadsicherheit zu verletzen, als wenn Sie i++ zurückgeben und sich daran erinnern (oder nicht), den richtigen Satz von Monitoren vorher abzurufen.

88
Andrzej Doyle

Wenn Sie sich die Methoden ansehen, die AtomicInteger hat, werden Sie feststellen, dass sie tendenziell den üblichen Vorgängen in Ints entsprechen. Zum Beispiel:

static AtomicInteger i;

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

ist die Thread-sichere Version davon:

static int i;

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

Die Methoden sind wie folgt:
++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

53
Powerlord

AtomicInteger wird hauptsächlich verwendet, wenn Sie sich in einem Multithread-Kontext befinden und Thread-sichere Vorgänge für eine ganze Zahl ausführen müssen, ohne synchronized zu verwenden. Die Zuweisung und der Abruf auf dem primitiven Typ int sind bereits atomar, aber AtomicInteger enthält viele Operationen, die auf int nicht atomar sind.

Die einfachsten sind die getAndXXX oder xXXAndGet. Zum Beispiel ist getAndIncrement() ein atomares Äquivalent zu i++, das nicht atomar ist, weil es eigentlich eine Abkürzung für drei Operationen ist: Abrufen, Hinzufügen und Zuweisen. compareAndSet ist sehr nützlich, um Semaphore, Sperren, Latches usw. zu implementieren.

Die Verwendung der Variablen AtomicInteger ist schneller und lesbarer als die 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 Atomtest in 3 Sekunden, der synchronisierte in etwa 5,5 Sekunden. Das Problem hierbei ist, dass die zu synchronisierende Operation (notAtomic++) wirklich kurz ist. Die Kosten für die Synchronisation sind also im Vergleich zur Operation sehr wichtig.

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

34
gabuzo

Zum Beispiel habe ich eine Bibliothek, die Instanzen einer Klasse generiert. Jede dieser Instanzen muss eine eindeutige ganzzahlige 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, die jedoch langsamer und weniger elegant ist.

14
Sergei Tachenov

Wie gesagt, benutze ich manchmal AtomicIntegers, wenn ich ein Int als Referenz übergeben möchte. Es handelt sich um eine integrierte Klasse mit architekturspezifischem Code. Daher ist sie einfacher und wahrscheinlich optimiert als jeder MutableInteger, den ich schnell codieren könnte. Das heißt, es fühlt sich an wie ein Missbrauch der Klasse.

6
David Ehrmann

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

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

Beide verwenden die updateFunction zur Aktualisierung des Atomwerts. 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 "Compare and Set" -Operationen durchzuführen als die Standardoperation. Es kann zum Beispiel überprüfen, dass der Atomzähler nicht unter Null geht, normalerweise müsste er synchronisiert werden, und hier ist der Code gesperrt:

    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 Example .

6
pwojnowski

Normalerweise verwende ich AtomicInteger, wenn ich Ids Objekten zuweisen muss, die von mehreren Threads aufgerufen oder erstellt werden können, und normalerweise verwende ich sie als statisches Attribut für die Klasse, auf die ich im Konstruktor der Objekte zugreifen kann.

4

Sie können nicht blockierende Sperren mithilfe von compareAndSwap (CAS) für atomare Ganzzahlen oder Longs implementieren. Die Transaktionsspeicher-Software " " Tl2 " " beschreibt dies:

Wir ordnen jedem transaktionierten .__ eine spezielle versionierte Schreibsperre zu. Speicherort. In seiner einfachsten Form ist die versionierte Schreibsperre eine Single-Word-Spinlock, der eine CAS-Operation zum Abrufen der Sperre und .__ verwendet. ein Laden, um es freizugeben. Da braucht man nur ein einzelnes Bit, um anzuzeigen Damit die Sperre aufgehoben wird, verwenden wir den Rest des Sperrworts, um eine .__ zu halten. Versionsnummer.

Was es beschreibt, wird zuerst die atomare Ganzzahl gelesen. Teilen Sie dies in ein ignoriertes Sperrbit und die Versionsnummer auf. Versuchen Sie, es als CAS zu schreiben, wenn das mit der aktuellen Versionsnummer gelöschte Sperrbit in den Sperrbitsatz und die nächste Versionsnummer geschrieben wird. Schleife, bis Sie erfolgreich sind und Sie sind der Thread, dem das Schloss gehört. Entsperren Sie durch Setzen der aktuellen Versionsnummer, wobei das Sperrbit gelöscht ist. In diesem Artikel wird beschrieben, wie die Versionsnummern in den Sperren verwendet werden, um zu koordinieren, dass Threads beim Schreiben einen konsistenten Satz von Lesevorgängen haben.

Dieser Artikel beschreibt, dass Prozessoren Hardwareunterstützung für Vergleichs- und Auslagerungsvorgänge haben, was den Betrieb sehr effizient macht. Sie behauptet auch:

nicht blockierende CAS-basierte Zähler, die atomare Variablen verwenden, haben bessere Leistung als schlossbasierte Zähler bei geringer bis mittlerer Konkurrenz

3
simbo1905

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

1
Michael Berry

Ich habe AtomicInteger verwendet, um das Problem des Essensphilosophen 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 ganze Zahl von 1 bis 5 identifiziert. Wenn eine Gabel von einem Philosophen verwendet wird, enthält AtomicInteger den Wert des Philosophen 1 bis 5. Andernfalls wird die Gabel nicht verwendet, daher ist der Wert von AtomicInteger -1 . 

Der AtomicInteger ermöglicht dann die Überprüfung, ob eine Gabelung frei ist (Wert == - 1), und sie in einer atomaren Operation auf den Besitzer der Gabelung gesetzt, wenn sie frei ist. Siehe Code unten.

AtomicInteger fork0 = neededForks[0];//neededForks is an array that holds the forks needed per Philosopher
AtomicInteger fork1 = neededForks[1];
while(true){    
    if (Hungry) {
        //if fork is free (==-1) then grab it by denoting who took it
        if (!fork0.compareAndSet(-1, p) || !fork1.compareAndSet(-1, p)) {
          //at least one fork was not succesfully grabbed, release both and try again later
            fork0.compareAndSet(p, -1);
            fork1.compareAndSet(p, -1);
            try {
                synchronized (lock) {//sleep and get notified later when a philosopher puts down one fork                    
                    lock.wait();//try again later, goes back up the loop
                }
            } catch (InterruptedException e) {}

        } else {
            //sucessfully grabbed both forks
            transition(fork_l_free_and_fork_r_free);
        }
    }
}

Da die Methode compareAndSet nicht blockiert, sollte sie den Durchsatz erhöhen und mehr Arbeit leisten. Wie Sie vielleicht wissen, wird das Problem von Dining Philosophers verwendet, wenn ein kontrollierter Zugriff auf Ressourcen erforderlich ist, d. H. Gabeln erforderlich sind, da ein Prozess Ressourcen benötigt, um seine Arbeit fortzusetzen. 

0
Rodrigo Gomez