programowanieProgramista Java middle/backend

Co wiesz o pracy z wątkami w Javie i jak zorganizować bezpieczeństwo wątków?

Zdaj rozmowy kwalifikacyjne z asystentem AI Hintsage

Odpowiedź.

W Javie praca z wątkami realizowana jest za pomocą klas Thread, Runnable oraz z pakietu java.util.concurrent. Aby zorganizować bezpieczeństwo wątków, stosuje się różne mechanizmy synchronizacji:

  • Blokady/metody synchronizowane (synchronized) pozwalają wielu wątkom na dostęp do wspólnych danych bez wyścigów.
  • volatile gwarantuje widoczność zmian zmiennej pomiędzy wątkami.
  • Klasy z java.util.concurrent (np. ReentrantLock, Semaphore, AtomicInteger, ConcurrentHashMap) zapewniają bardziej elastyczne sposoby synchronizacji.

Przykład synchronizacji:

public class Counter { private int count = 0; public synchronized void increment() { count++; } public int getCount() { return count; } }

Przykład z użyciem klas atomowych:

import java.util.concurrent.atomic.AtomicInteger; public class AtomicCounter { private AtomicInteger count = new AtomicInteger(0); public void increment() { count.incrementAndGet(); } public int getCount() { return count.get(); } }

Pytanie z pułapką.

Czy kluczowe słowo volatile gwarantuje bezpieczeństwo wątków przy inkrementacji licznika typu int?

Odpowiedź: Nie. volatile zapewnia tylko widoczność wartości między wątkami, ale nie atomowość operacji. Inkrementacja nie jest operacją atomową (count++ składa się z odczytu, zwiększenia i zapisu), dlatego możliwe są straty danych. Dla bezpiecznej inkrementacji wątkowej potrzebna jest synchronizacja lub klasy takie jak AtomicInteger.

volatile int count = 0; // count++ nie jest bezpieczne dla wątków!

Przykłady realnych błędów z powodu niewiedzy o szczegółach tematu.


Historia

W sklepie internetowym bonusowy rachunek był aktualizowany przez licznik volatile. Pod dużym obciążeniem tysiące użytkowników zamawiały towary, a część bonusów znikała z powodu wyścigu wątków przy count++.


Historia

Pracownik używał zwykłej ArrayList jako wspólnego bufora między producentem a konsumentem w aplikacji wielowątkowej, w wyniku czego pojawiały się ConcurrentModificationException. Rozwiązaniem było zastosowanie bloków synchronized lub zamiana na CopyOnWriteArrayList.


Historia

W przetwarzaniu płatności, programista wykonywał operację "sprawdź i pobierz kwotę" przez dwie niezwiązane metody. W warunkach dużego obciążenia prowadziło to do podwójnego obciążenia, dopóki nie wprowadzono atomowych transakcji i blokad.