Istnieją dwa główne zastosowania AtomicInteger
:
Jako licznik atomowy ( incrementAndGet()
itp.), Który może być używany jednocześnie przez wiele wątków
Jako prymityw, który obsługuje instrukcje porównywania i zamiany ( compareAndSet()
) do implementacji algorytmów nieblokujących.
Oto przykład nieblokującego generatora liczb losowych z Java Concordrency Briana Göetza 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 inkrementacji (i przetwarza wynik przed powrotem).
read
iwrite that value + 1
, zostanie to wykryte, a nie zastąpi starą aktualizację (unikając problemu „utraconej aktualizacji”). Jest to w rzeczywistości szczególny przypadekcompareAndSet
- jeśli stara wartość była2
, klasa faktycznie wywołujecompareAndSet(2, 3)
- więc jeśli inny wątek zmodyfikował wartość w międzyczasie, metoda przyrostowa zostanie zrestartowana od początku.Absolutnie najprostszym przykładem, jaki mogę wymyślić, jest zwiększenie operacji atomowej.
Ze standardowymi ints:
Z AtomicInteger:
Ten ostatni jest bardzo prostym sposobem na wykonanie prostych efektów mutacji (zwłaszcza zliczanie lub indeksowanie unikalne), bez konieczności uciekania się do synchronizacji całego dostępu.
Bardziej złożoną logikę bez synchronizacji można zastosować
compareAndSet()
jako rodzaj optymistycznego blokowania - uzyskaj bieżącą wartość, oblicz wynik na podstawie tego, ustaw ten wynik wartość iff jest nadal danymi wejściowymi używanymi do obliczeń, w przeciwnym razie zacznij od nowa - ale przykłady zliczania są bardzo przydatne i często używamAtomicIntegers
do zliczania i unikatowych generatorów maszyn wirtualnych, jeśli istnieje jakakolwiek wskazówka na wiele wątków, ponieważ są one tak łatwe w obsłudze, że prawie uważam za przedwczesną optymalizację, aby używać zwykłegoints
.Chociaż prawie zawsze można osiągnąć te same gwarancje synchronizacji
ints
i odpowiedniesynchronized
deklaracje, pięknoAtomicInteger
polega na tym, że bezpieczeństwo wątków jest wbudowane w sam obiekt, zamiast martwić się możliwymi przeplotami i monitorami każdej metody tak się dzieje, aby uzyskać dostęp doint
wartości. Znacznie trudniej jest przypadkowo naruszyć bezpieczeństwo wątków podczas połączeniagetAndIncrement()
niż podczas powrotui++
i zapamiętywania (lub nie) uzyskania wcześniej odpowiedniego zestawu monitorów.źródło
Jeśli spojrzysz na metody, które ma AtomicInteger, zauważysz, że zwykle odpowiadają one typowym operacjom na ints. Na przykład:
to wersja tego wątku:
Metody map w następujący sposób:
++i
isi.incrementAndGet()
i++
isi.getAndIncrement()
--i
isi.decrementAndGet()
i--
isi.getAndDecrement()
i = x
isi.set(x)
x = i
isx = i.get()
Istnieją również inne metody wygody, takie jak
compareAndSet
lubaddAndGet
źródło
Podstawowym zastosowaniem
AtomicInteger
jest, gdy jesteś w kontekście wielowątkowym i musisz wykonywać bezpieczne operacje wątkowe na liczbie całkowitej bez użyciasynchronized
. Przypisywanie i wyszukiwanie typu pierwotnegoint
jest już atomowe, aleAtomicInteger
obejmuje wiele operacji, które nie są atomoweint
.Najprostsze to
getAndXXX
lubxXXAndGet
. Na przykładgetAndIncrement()
jest atomowym odpowiednikiem,i++
który nie jest atomowy, ponieważ w rzeczywistości jest skrótem do trzech operacji: pobierania, dodawania i przypisywania.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:
Na moim komputerze z Javą 1.6 test atomowy uruchamia się w 3 sekundy, a synchronizowany w około 5,5 sekundy. Problem polega na tym, że operacja synchronizacji (
notAtomic++
) jest naprawdę krótka. Koszt synchronizacji jest więc naprawdę ważny w porównaniu z operacją.Oprócz atomowości AtomicInteger może być używany jako modyfikowalna wersja
Integer
na przykładMap
ws jako wartości.źródło
AtomicInteger
jako klucza mapy, ponieważ używa domyślnejequals()
implementacji, co prawie na pewno nie jest tym, czego można oczekiwać od semantyki, jeśli jest używana w mapie.Na przykład mam bibliotekę, która generuje instancje niektórych klas. Każda z tych instancji musi mieć unikalny identyfikator liczby całkowitej, ponieważ instancje te reprezentują polecenia wysyłane do serwera, a każda komenda musi mieć unikalny identyfikator. Ponieważ wiele wątków może jednocześnie wysyłać polecenia, do generowania tych identyfikatorów używam AtomicInteger. Alternatywnym podejściem byłoby użycie pewnego rodzaju blokady i zwykłej liczby całkowitej, ale jest to zarówno wolniejsze, jak i mniej eleganckie.
źródło
Jak powiedział gabuzo, czasami używam AtomicIntegers, gdy chcę przekazać int przez referencję. Jest to klasa wbudowana, która ma kod specyficzny dla architektury, więc jest łatwiejsza i prawdopodobnie bardziej zoptymalizowana niż jakikolwiek MutableInteger, który mógłbym szybko zakodować. To powiedziawszy, to jest nadużycie klasy.
źródło
W Javie 8 klasy atomowe zostały rozszerzone o dwie interesujące funkcje:
Obaj używają funkcji updateFunction, aby przeprowadzić aktualizację wartości atomowej. Różnica polega na tym, że pierwsza zwraca starą wartość, a druga zwraca nową wartość. Funkcja updateFunction może być zaimplementowana w celu wykonywania bardziej złożonych operacji „porównywania i ustawiania” niż standardowa. Na przykład może sprawdzić, czy licznik atomowy nie spadnie poniżej zera, normalnie wymagałoby synchronizacji, a tutaj kod jest bez blokady:
Kod pochodzi z Java Atomic Example .
źródło
Zwykle używam AtomicInteger, gdy muszę podać identyfikatory obiektom, które można uzyskać lub utworzyć z wielu wątków, i zwykle używam go jako statycznego atrybutu klasy, do której uzyskuję dostęp w konstruktorze obiektów.
źródło
Możesz zaimplementować blokady nieblokujące za pomocą CompareAndSwap (CAS) na liczbach całkowitych lub długich atomach. „TL2” Oprogramowanie transakcyjna pamięci artykule opisano poniżej:
To, co opisuje, to najpierw przeczytaj liczbę całkowitą atomową. Podziel to na zignorowany bit blokady i numer wersji. Spróbuj napisać CAS, gdy bit blokady zostanie wyczyszczony z bieżącym numerem wersji do zestawu bitów blokady i kolejnym numerem wersji. Pętla, dopóki nie odniesiesz sukcesu, a twój wątek jest właścicielem zamka. Odblokuj, ustawiając bieżący numer wersji z wyczyszczonym bitem blokady. Artykuł opisuje używanie numerów wersji w blokadach w celu skoordynowania, że wątki mają spójny zestaw odczytów podczas pisania.
W tym artykule opisano, że procesory mają sprzętową obsługę operacji porównywania i zamiany, dzięki czemu są bardzo wydajne. Twierdzi także:
źródło
Kluczem jest to, że umożliwiają bezpieczny jednoczesny dostęp i modyfikację. 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 zsynchronizowanych blokach.
źródło
Użyłem AtomicInteger, aby rozwiązać problem z filozofem restauracji.
W moim rozwiązaniu instancje AtomicInteger zostały użyte do przedstawienia widelców, potrzebne są dwa na filozofa. Każdy filozof jest identyfikowany jako liczba całkowita, od 1 do 5. Kiedy filozof używa rozwidlenia, AtomicInteger przechowuje wartość filozofa od 1 do 5, w przeciwnym razie rozwidlenie nie jest używane, więc wartość AtomicInteger wynosi -1 .
AtomicInteger pozwala następnie sprawdzić, czy widelec jest wolny, wartość == - 1, i ustawić go na właściciela widelca, jeśli jest wolny, w jednej operacji atomowej. Zobacz kod poniżej.
Ponieważ metoda CompareAndSet nie blokuje, powinna zwiększyć przepustowość, wykonać więcej pracy. Jak zapewne wiesz, problem filozofów gastronomicznych jest wykorzystywany, gdy potrzebny jest kontrolowany dostęp do zasobów, tj. Widelce, tak jak proces potrzebuje zasobów do kontynuowania pracy.
źródło
Prosty przykład funkcji CompareAndSet ():
Wydrukowano: poprzednia wartość: 0 Wartość została zaktualizowana i wynosi 6 Kolejny prosty przykład:
Wydrukowano: Poprzednia wartość: 0 Wartość nie została zaktualizowana
źródło