0Pricing
Java Academy · Lekcja

Zmienne atomowe: aktualizacje bez blokad

Używaj AtomicInteger, AtomicLong i AtomicReference do bezpiecznych wątkowo liczników bez blokad.

Zmienne atomowe: aktualizacje bez blokad to bezpłatna lekcja Java Academy na CoddyKit. To lekcja 3 z 4. Możesz przeczytać całą lekcję poniżej za darmo — a potem ćwiczyć ją interaktywnie w przeglądarce z wbudowanym edytorem kodu i tutorem AI dostępnym 24/7. To część ścieżki edukacyjnej Java Academy, a Twój postęp synchronizuje się między webem a aplikacją CoddyKit. Kurs Java Academy zawiera 4 lekcji w sumie.

Problem z operacjami nieatomowymi

Proste zwiększenie count++ NIE jest bezpieczne dla wątków — obejmuje trzy operacje: odczyt, dodawanie i zapis. Współbieżne wątki mogą przeplatać te operacje, powodując utratę aktualizacji. Zmienne atomowe rozwiązują ten problem bez używania blokad.

// UNSAFE:
int count = 0;
// Thread A reads count=0, Thread B reads count=0
// Both write 1 — second update lost!
count++; // NOT thread-safe

// SAFE:
import java.util.concurrent.atomic.*;
AtomicInteger atomicCount = new AtomicInteger(0);
atomicCount.incrementAndGet(); // atomic, lock-free

Podstawy AtomicInteger

AtomicInteger udostępnia operacje na liczbach całkowitych, których atomowość jest gwarantowana przez instrukcje procesora compare-and-swap (CAS) — nie są potrzebne blokady.

AtomicInteger counter = new AtomicInteger(0);

counter.set(10);                   // set to 10
System.out.println(counter.get()); // 10

int prev = counter.getAndIncrement(); // returns 10, sets to 11
int curr = counter.incrementAndGet(); // sets to 12, returns 12
counter.addAndGet(5);                 // adds 5, returns 17
counter.decrementAndGet();            // subtracts 1, returns 16

compareAndSet: operacja CAS

compareAndSet(expected, update) atomowo aktualizuje wartość tylko wtedy, gdy jest ona obecnie równa oczekiwanej wartości. Zwraca true, jeśli operacja się powiedzie. To podstawa wszystkich algorytmów bez blokad.

AtomicInteger ai = new AtomicInteger(5);

boolean updated = ai.compareAndSet(5, 10); // current==5? set to 10
System.out.println(updated); // true
System.out.println(ai.get()); // 10

boolean failed = ai.compareAndSet(5, 20); // current==10, not 5 → fails
System.out.println(failed); // false
System.out.println(ai.get()); // still 10

AtomicLong do liczników

AtomicLong jest odpowiednikiem dla wartości 64-bitowych. Jest często używany do generatorów sekwencji i liczników trafień:

AtomicLong seq = new AtomicLong(0);

// Thread-safe sequence generator:
long nextId() {
    return seq.incrementAndGet();
}

System.out.println(nextId()); // 1
System.out.println(nextId()); // 2
System.out.println(nextId()); // 3

AtomicBoolean

Bezpieczna dla wątków flaga logiczna. Jest często używana do jednorazowej inicjalizacji lub sygnalizowania zamknięcia:

AtomicBoolean initialized = new AtomicBoolean(false);

void initOnce() {
    // compareAndSet: only first caller succeeds
    if (initialized.compareAndSet(false, true)) {
        System.out.println("Initializing...");
        // perform expensive init
    } else {
        System.out.println("Already initialized");
    }
}

AtomicReference

AtomicReference<V> zapewnia atomowe aktualizacje referencji do obiektu. Jest używany do implementowania struktur danych bez blokad:

import java.util.concurrent.atomic.*;

record Config(String host, int port) {}

AtomicReference<Config> configRef =
    new AtomicReference<>(new Config("localhost", 8080));

// Hot-swap the configuration atomically:
Config oldConfig = configRef.get();
Config newConfig = new Config("prod.example.com", 443);

boolean swapped = configRef.compareAndSet(oldConfig, newConfig);
System.out.println(swapped); // true
System.out.println(configRef.get()); // Config[host=prod.example.com, port=443]

updateAndGet / getAndUpdate

Atomowo zastosuj funkcję, korzystając wewnętrznie z pętli ponawiania CAS:

AtomicInteger value = new AtomicInteger(10);

// Apply function atomically: square the value
int newVal = value.updateAndGet(x -> x * x);
System.out.println(newVal); // 100

// Get old, then apply:
int old = value.getAndUpdate(x -> x + 1);
System.out.println(old);   // 100
System.out.println(value.get()); // 101

accumulateAndGet

Połącz bieżącą wartość z podaną wartością za pomocą funkcji:

AtomicInteger max = new AtomicInteger(0);

// Keep running maximum:
max.accumulateAndGet(42, Math::max); // max(0, 42) = 42
max.accumulateAndGet(15, Math::max); // max(42, 15) = 42
max.accumulateAndGet(99, Math::max); // max(42, 99) = 99

System.out.println(max.get()); // 99

LongAdder do liczników z dużą rywalizacją

Przy dużej rywalizacji wątków ponawianie CAS przez AtomicLong może pogorszyć wydajność. LongAdder używa wielu komórek, aby zmniejszyć rywalizację:

import java.util.concurrent.atomic.*;

LongAdder adder = new LongAdder();

// Multiple threads can call add() with minimal contention:
adder.increment();
adder.add(5);
adder.increment();

System.out.println(adder.sum()); // 7
// Note: sum() is NOT atomic with add() — use only when no concurrent adds

Tablice atomowe

AtomicIntegerArray / AtomicLongArray zapewniają operacje atomowe na poszczególnych elementach tablicy:

AtomicIntegerArray arr = new AtomicIntegerArray(5);

arr.set(0, 10);
arr.incrementAndGet(0);
arr.compareAndSet(2, 0, 42); // index 2: if 0, set 42

System.out.println(arr.get(0)); // 11
System.out.println(arr.get(2)); // 42

Kiedy używać zmiennych atomowych, a kiedy blokad

Należy używać zmiennych atomowych, gdy:

  • aktualizowana jest pojedyncza zmienna, np. licznik, flaga lub referencja
  • występuje niewielka rywalizacja
  • ważna jest wydajność bez blokad

Należy używać blokad, gdy:

  • wiele zmiennych musi być aktualizowanych atomowo
  • potrzebna jest złożona logika warunkowa
  • występuje bardzo duża rywalizacja (ponawianie CAS pogarsza wydajność)

Szybkie sprawdzenie

Co gwarantuje compareAndSet(expected, update)?

Podsumowanie: zmienne atomowe

Najważniejsze informacje:

  • Typy atomowe używają instrukcji procesora CAS — są bez blokad i bezpieczne dla wątków
  • AtomicInteger/Long/Boolean/Reference zapewniają atomowość pojedynczej zmiennej
  • compareAndSet jest podstawą algorytmów bez blokad
  • updateAndGet/accumulateAndGet służą do aktualizacji funkcyjnych
  • LongAdder sprawdza się w scenariuszach liczników z dużą rywalizacją

Często zadawane pytania

Czy lekcja „Zmienne atomowe: aktualizacje bez blokad” jest bezpłatna?

Tak — pełny tekst „Zmienne atomowe: aktualizacje bez blokad” jest dostępny za darmo tutaj w sieci. Aby ćwiczyć ją interaktywnie (wbudowany edytor kodu i tutor AI dostępny 24/7) i odblokować resztę kursu Java Academy, przejdź na CoddyKit PRO. Kurs Java Academy zawiera 4 lekcji w sumie.

Co nauczysz się w „Zmienne atomowe: aktualizacje bez blokad”?

Używaj AtomicInteger, AtomicLong i AtomicReference do bezpiecznych wątkowo liczników bez blokad. Ćwiczysz Java Academy z praktycznym kodem, który uruchamiasz bezpośrednio w przeglądarce, a tutor AI dostępny 24/7 odpowiada na Twoje pytania podczas pracy nad lekcją.

Czy potrzebuję doświadczenia, aby zacząć Java Academy?

Nie wymagamy żadnego doświadczenia. Java Academy w CoddyKit jest strukturyzowany dla początkujących i zaawansowanych użytkowników, więc możesz zacząć tutaj lub od początku i uczyć się w swoim tempie. To lekcja 3 z 4.

Ile czasu zajmuje lekcja „Zmienne atomowe: aktualizacje bez blokad”?

Większość lekcji CoddyKit trwa około 5–10 minut. Każda lekcja to mały, interaktywny krok, dzięki czemu robisz systematyczne postępy i zawsze wracasz dokładnie do tego samego miejsca — na webie i w aplikacji.

Czy mogę pisać i uruchamiać kod w tej lekcji Java Academy?

Tak. Każda lekcja Java Academy zawiera wbudowany edytor kodu, więc piszesz i uruchamiasz prawdziwy kod bezpośrednio w przeglądarce i od razu otrzymujesz sprzężenie zwrotne od AI — bez konfiguracji na komputerze.

Wszystkie lekcje w tym kursie

  1. ReentrantLock a synchronized
  2. ReadWriteLock w scenariuszach czytelnik-pisarz
  3. Zmienne atomowe: aktualizacje bez blokad
  4. StampedLock i optymistyczne odczyty
← Powrót do Java Academy