Praktyczne zastosowania AtomicInteger

W pewnym sensie rozumiem, że AtomicInteger i inne zmienne atomowe umożliwiają współbieżny dostęp. W jakich przypadkach ta klasa jest zazwyczaj używana?

Author: informatik01, 2011-01-27

10 answers

Istnieją dwa główne zastosowania AtomicInteger:

  • Jako licznik atomowy (incrementAndGet(), etc), który może być używany przez wiele wątków jednocześnie

  • Jako prymityw, który obsługujecompare-and-swap instruction (compareAndSet()) do implementacji nieblokujących algorytmów.

    Oto przykład nieblokującego generatora liczb losowych z Briana Göetza współbieżność Javy w praktyce :

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

    Jak widać, w zasadzie działa prawie tak samo jak incrementAndGet(), ale wykonuje dowolne obliczenia (calculateNext()) zamiast przyrostu(i przetwarza wynik przed powrotem).

 161
Author: axtavt,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-27 16:29:08

Absolutnym najprostszym przykładem, jaki przychodzi mi do głowy, jest wykonanie operacji atomowej.

Ze standardowymi intami:

private volatile int counter;

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

Z AtomicInteger:

private AtomicInteger counter;

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

Ten ostatni jest bardzo prostym sposobem wykonywania prostych efektów mutacji (zwłaszcza zliczania, lub unikalnego indeksowania), bez konieczności uciekania się do synchronizacji całego dostępu.

Bardziej skomplikowana logika bez synchronizacji może być zastosowana przez użycie compareAndSet() jako typu blokowania optymistycznego-uzyskaj aktualną wartość, Oblicz wynik na tej podstawie, ustaw ten wynik iff wartość jest nadal wejściem używanym do obliczeń, w przeciwnym razie zacznij od nowa - ale przykłady liczenia są bardzo przydatne i często będę używał AtomicIntegers do liczenia i unikalnych generatorów dla maszyn wirtualnych, Jeśli pojawi się jakaś wskazówka dotycząca wielu wątków, ponieważ są tak łatwe w obsłudze, że prawie uznałbym za przedwczesną optymalizację użycie zwykłego ints.

Podczas gdy prawie zawsze można osiągnąć te same gwarancje synchronizacji z ints i odpowiednie synchronized deklaracje, piękno AtomicInteger polega na tym, że bezpieczeństwo wątku jest wbudowane w rzeczywisty obiekt, a nie musisz martwić się o ewentualne przeplotki i monitory każdej metody, która ma dostęp do wartości int. O wiele trudniej jest przypadkowo naruszyć threadsafety podczas wywoływania getAndIncrement() niż po powrocie i++ i zapamiętywaniu (lub nie), aby wcześniej zdobyć odpowiedni zestaw monitorów.

 86
Author: Andrzej Doyle,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-27 16:17:30

Jeśli spojrzysz na metody atomicinteger, zauważysz, że zwykle odpowiadają one powszechnym operacjom na Int. Na przykład:

static AtomicInteger i;

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

Jest bezpieczną dla wątku wersją tego:

static int i;

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

Metody mapują tak:
++i na i.incrementAndGet()
i++ na i.getAndIncrement()
--i na i.decrementAndGet()
i-- na i.getAndDecrement()
i = x na i.set(x)
x = i jest x = i.get()

Istnieją również inne metody wygody, takie jak compareAndSet lub addAndGet

 51
Author: Powerlord,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-27 16:37:30

Podstawowe użycie AtomicInteger jest wtedy, gdy znajdujesz się w kontekście wielowątkowym i musisz wykonać bezpieczne operacje wątku na liczbie całkowitej bez użycia synchronized. Przypisywanie i pobieranie na typ prymitywny {[3] } są już atomowe, ale AtomicInteger pochodzi z wielu operacji, które nie są atomowe na int.

Najprostsze są getAndXXX lub xXXAndGet. Na przykład {[8] } jest atomowym odpowiednikiem i++, który nie jest atomowy, ponieważ jest skrótem dla trzech operacji: odzyskiwanie, dodawanie i przypisywanie. compareAndSet jest bardzo przydatny do implementacji semaforów, zamków, zatrzasków itp.

Używanie AtomicInteger jest szybsze i bardziej czytelne niż wykonywanie tego samego przy użyciu synchronizacji.

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

Na moim komputerze z Javą 1.6 test atomowy przebiega w 3 sekundy, podczas gdy zsynchronizowany w około 5,5 sekundy. Problem polega na tym, że operacja synchronizacji (notAtomic++) jest naprawdę krótka. Więc koszt synchronizacji jest naprawdę ważny w porównaniu do operacji.

Obok atomicity AtomicInteger może być używany jako zmienna Wersja Integer na przykład w Map S jako wartości.

 33
Author: gabuzo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-27 17:31:06

Na przykład, mam bibliotekę, która generuje instancje jakiejś klasy. Każda z tych instancji musi mieć unikalny integer ID, ponieważ te instancje reprezentują polecenia wysyłane na serwer, a każde polecenie musi mieć unikalny ID. Ponieważ wiele wątków może jednocześnie wysyłać polecenia, używam AtomicInteger do generowania tych identyfikatorów. Alternatywnym podejściem byłoby użycie jakiegoś zamka i zwykłej liczby całkowitej, ale jest to zarówno wolniejsze, jak i mniej eleganckie.

 14
Author: Sergei Tachenov,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-27 16:13:31

Jak powiedział gabuzo, czasami używam AtomicIntegers, gdy chcę przekazać int przez odniesienie. Jest to wbudowana klasa, która ma kod specyficzny dla architektury, więc jest łatwiejsza i prawdopodobnie bardziej zoptymalizowana niż jakikolwiek MutableInteger, który mógłbym szybko kodować. To powiedziawszy, czuję się jak nadużycie klasy.

 6
Author: David Ehrmann,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-03-06 08:20:22

W Javie rozszerzono 8 klas atomowych o dwie ciekawe funkcje:

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

Oba używają updateFunction, aby wykonać aktualizację wartości atomowej. Różnica polega na tym, że pierwszy zwraca starą wartość, a drugi zwraca nową. Funkcja updateFunction może być zaimplementowana w celu wykonywania bardziej złożonych operacji "porównaj i ustaw" niż standard jeden. Na przykład może sprawdzić, czy licznik atomowy nie spada poniżej zera, normalnie wymagałby synchronizacji, a tutaj kod jest wolny od blokady:

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

Kod pochodzi z Java Atomic Example.

 6
Author: pwojnowski,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2015-12-03 09:20:59

Zwykle używam AtomicInteger, gdy muszę nadać ID obiektom, które mogą być accesed lub utworzone z wielu wątków, i zwykle używam go jako statycznego atrybutu na klasie, do której uzyskuję dostęp w konstruktorze obiektów.

 4
Author: DguezTorresEmmanuel,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2013-03-26 19:01:38

Możesz zaimplementować nieblokujące blokady używając compareAndSwap (CAS)na liczbach atomowych lub długich. W artykule Pamięć transakcyjna oprogramowania"Tl2" opisano TAK:

Z każdą transakcją kojarzymy specjalną wersjonowaną blokadę zapisu Miejsce Pamięci. W najprostszej formie wersjonowany write-lock jest jednowierszowy spinlock, który wykorzystuje operację CAS do zdobycia zamka i sklep, żeby go wypuścić. Ponieważ wystarczy tylko jeden bit, aby wskazać że zamek jest zajęty, używamy reszty słowa lock do trzymania numer wersji.

To, co opisuje, to najpierw odczytana liczba atomowa. Podziel to na ignorowany bit blokady i numer wersji. Spróbuj zapisać go jako bit blokady wyczyszczony z bieżącym numerem wersji do ustawionego bitu blokady i następnym numerem wersji. Pętla, dopóki ci się nie uda, a Twój wątek jest właścicielem blokady. Odblokuj poprzez ustawienie bieżącego numeru wersji z bitem blokady wyczyszczonym. W artykule opisano za pomocą numery wersji w blokadach, aby koordynować, że wątki mają spójny zestaw odczytów podczas zapisu.

Ten artykuł opisuje, że procesory mają wsparcie sprzętowe dla operacji porównywania i wymiany, dzięki czemu są bardzo wydajne. Twierdzi również:

Nieblokujące liczniki oparte na CAS wykorzystujące zmienne atomowe mają lepsze wydajność niż liczniki oparte na blokadzie w niskiej do umiarkowanej konkurencji

 3
Author: simbo1905,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2012-09-17 20:26:16

Kluczem jest to, że umożliwiają one bezpieczny dostęp i modyfikację jednocześnie. Są one powszechnie używane jako liczniki w środowisku wielowątkowym - przed ich wprowadzeniem musiała to być Klasa napisana przez użytkownika, która zawierała różne metody w zsynchronizowane bloki.

 1
Author: Michael Berry,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/doraprojects.net/template/agent.layouts/content.php on line 54
2011-01-27 16:10:49