Szukaj…


Wprowadzenie

Typy atomowe Java to proste typy zmienne, które zapewniają podstawowe operacje bezpieczne dla wątków i atomowe bez uciekania się do blokowania. Są przeznaczone do stosowania w przypadkach, w których blokowanie byłoby wąskim gardłem współbieżności lub gdy istnieje ryzyko impasu lub blokady na żywo.

Parametry

Parametr Opis
zestaw Zmienny zestaw pola
otrzymać Zmienny odczyt pola
lazySet Jest to operacja zamówiona w sklepie
CompareAndSet Jeśli wartość jest wartością wydatkowaną, należy wysłać ją do nowej wartości
getAndSet pobierz aktualną wartość i zaktualizuj

Uwagi

Wiele w zasadzie kombinacji lotnych odczytów lub zapisów oraz operacji CAS . Najlepszym sposobem na zrozumienie tego jest bezpośrednie spojrzenie na kod źródłowy. Np AtomicInteger , Unsafe.getAndSet

Tworzenie typów atomowych

W przypadku prostego kodu wielowątkowego dopuszczalna jest synchronizacja . Jednak korzystanie z synchronizacji ma wpływ na żywotność, a ponieważ baza kodów staje się bardziej złożona, wzrasta prawdopodobieństwo, że skończysz z Deadlock , Starvation lub Livelock .

W przypadkach bardziej złożonej współbieżności użycie Zmiennych Atomowych jest często lepszą alternatywą, ponieważ pozwala na dostęp do poszczególnych zmiennych w sposób bezpieczny dla wątków bez konieczności stosowania metod zsynchronizowanych lub bloków kodu.

Tworzenie typu AtomicInteger :

AtomicInteger aInt = new AtomicInteger() // Create with default value 0

AtomicInteger aInt = new AtomicInteger(1) // Create with initial value 1

Podobnie w przypadku innych typów instancji.

AtomicIntegerArray aIntArray = new AtomicIntegerArray(10) // Create array of specific length
AtomicIntegerArray aIntArray = new AtomicIntegerArray(new int[] {1, 2, 3}) // Initialize array with another array

Podobnie w przypadku innych typów atomów.

Istnieje znaczący wyjątek, że nie ma typów float i double . Można je symulować za pomocą Float.floatToIntBits(float) i Float.intBitsToFloat(int) dla float a także Double.doubleToLongBits(double) i Double.longBitsToDouble(long) dla dubletów.

Jeśli chcesz użyć sun.misc.Unsafe , możesz użyć dowolnej prymitywnej zmiennej jako atomowej, używając operacji atomowej w sun.misc.Unsafe . Wszystkie pierwotne typy powinny zostać przekonwertowane lub zakodowane w int lub longs, aby można było z nich korzystać w ten sposób. Aby uzyskać więcej informacji na ten temat, patrz: sun.misc.Unsafe .

Motywacja dla typów atomowych

Prostym sposobem na implementację aplikacji wielowątkowych jest użycie wbudowanych prymitywów synchronizacji i blokowania Java; np. synchronized słowo kluczowe. Poniższy przykład pokazuje, w jaki sposób możemy wykorzystać synchronized do zliczania liczby.

public class Counters {
    private final int[] counters;

    public Counters(int nosCounters) {
        counters = new int[nosCounters];
    }

    /**
     * Increments the integer at the given index
     */
    public synchronized void count(int number) {
        if (number >= 0 && number < counters.length) {
            counters[number]++;
        }
    }

    /**
     * Obtains the current count of the number at the given index,
     * or if there is no number at that index, returns 0.
     */
    public synchronized int getCount(int number) {
        return (number >= 0 && number < counters.length) ? counters[number] : 0;
    }
}

Ta implementacja będzie działać poprawnie. Jeśli jednak masz wiele wątków wykonujących wiele jednoczesnych wywołań w tym samym obiekcie Counters , synchronizacja może być wąskim gardłem. Konkretnie:

  1. Każde synchronized wywołanie metody rozpocznie się od tego, że bieżący wątek uzyska blokadę dla instancji Counters .
  2. Wątek przytrzyma blokadę podczas sprawdzania wartości number i aktualizacji licznika.
  3. Wreszcie zwolni blokadę, umożliwiając dostęp do innych wątków.

Jeśli jeden wątek próbuje uzyskać blokadę, podczas gdy inny go trzyma, wątek próbujący zostanie zablokowany (zatrzymany) w kroku 1 do momentu zwolnienia blokady. Jeśli czeka wiele wątków, jeden z nich je otrzyma, a pozostałe będą nadal blokowane.

Może to prowadzić do kilku problemów:

  • Jeśli blokada jest bardzo sporna (np. Wiele wątków próbuje ją zdobyć), niektóre wątki mogą być blokowane na długi czas.

  • Gdy wątek jest blokowany w oczekiwaniu na blokadę, system operacyjny zazwyczaj próbuje przełączyć wykonanie na inny wątek. To przełączanie kontekstu powoduje stosunkowo duży wpływ na wydajność procesora.

  • Gdy na tym samym zamku jest zablokowanych wiele wątków, nie ma gwarancji, że którykolwiek z nich zostanie potraktowany „sprawiedliwie” (tzn. Gwarantuje się, że każdy wątek zostanie zaplanowany). Może to prowadzić do głodu nici .

Jak wdrożyć typy atomowe?

Zacznijmy od przepisania powyższego przykładu przy użyciu liczników AtomicInteger :

public class Counters {
    private final AtomicInteger[] counters;

    public Counters(int nosCounters) {
        counters = new AtomicInteger[nosCounters];
        for (int i = 0; i < nosCounters; i++) {
            counters[i] = new AtomicInteger();
        }
    }

    /**
     * Increments the integer at the given index
     */
    public void count(int number) {
        if (number >= 0 && number < counters.length) {
            counters[number].incrementAndGet();
        }
    }

    /**
     * Obtains the current count of the object at the given index,
     * or if there is no number at that index, returns 0.
     */
    public int getCount(int number) {
        return (number >= 0 && number < counters.length) ? 
                counters[number].get() : 0;
    }
}

AtomicInteger[] int[] na AtomicInteger[] i zainicjowaliśmy go instancją w każdym elemencie. Dodaliśmy również wywołania incrementAndGet() i get() zamiast operacji na wartościach int .

Ale najważniejsze jest to, że możemy usunąć synchronized słowo kluczowe, ponieważ blokowanie nie jest już wymagane. Działa to, ponieważ operacje incrementAndGet() i get()atomowe i bezpieczne dla wątków . W tym kontekście oznacza to, że:

  • Każdy licznik w tablicy będzie obserwowalny tylko w stanie „przed” operacji (jak „przyrost”) lub w stanie „po”.

  • Zakładając, że operacja ma miejsce w czasie T , żaden wątek nie będzie w stanie zobaczyć stanu „przed” po czasie T

Ponadto, podczas gdy dwa wątki mogą faktycznie próbować zaktualizować tę samą instancję AtomicInteger w tym samym czasie, implementacje operacji zapewniają, że w danym przypadku występuje tylko jeden przyrost. Odbywa się to bez blokowania, co często skutkuje lepszą wydajnością.

Jak działają typy atomowe?

Typy atomowe zwykle polegają na specjalistycznych instrukcjach sprzętowych w zestawie instrukcji maszyny docelowej. Na przykład zestawy instrukcji oparte na procesorach Intel zawierają instrukcję CAS ( porównaj i zamień ), która wykona określoną sekwencję operacji pamięciowych atomowo.

Te instrukcje niskiego poziomu są używane do implementacji operacji wyższego poziomu w interfejsach API odpowiednich klas AtomicXxx . Na przykład (ponownie w pseudokodzie podobnym do C):

private volatile num;

int increment() {
  while (TRUE) {
    int old = num;
    int new = old + 1;
    if (old == compare_and_swap(&num, old, new)) {
      return new;
    }
  }
}

Jeśli nie ma rywalizacji na AtomicXxxx , test if powiedzie się i pętla natychmiast się zakończy. Jeśli istnieje spór, wtedy if nie powiedzie się dla wszystkich wątków oprócz jednego i będą „wirowały” w pętli przez niewielką liczbę cykli pętli. W praktyce spinowanie jest o rząd wielkości szybsze (z wyjątkiem nierealistycznie wysokich poziomów rywalizacji, gdzie synchronizacja działa lepiej niż klasy atomowe, ponieważ gdy operacja CAS się nie powiedzie, wówczas ponowna próba doda tylko więcej rywalizacji) niż zawieszenie wątku i przejście na inny jeden.

Nawiasem mówiąc, instrukcje JVM są zwykle używane przez JVM do wdrożenia niezamierzonego blokowania . Jeśli JVM zobaczy, że blokada nie jest obecnie zablokowana, spróbuje użyć CAS w celu uzyskania blokady. Jeśli CAS się powiedzie, nie ma potrzeby wykonywania kosztownego planowania wątków, przełączania kontekstu i tak dalej. Aby uzyskać więcej informacji na temat zastosowanych technik, zobacz Blokowane blokowanie w HotSpot .



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow