Przygotowanie do rozmowy rekrutacyjnej Java backend

Pytania i odpowiedzi rekrutacyjne Java 2026

Zestaw 30 pytań rekrutacyjnych Java dla programistów backendowych, obejmujący zagadnienia od podstaw języka i działania JVM po kolekcje, Stream API i współbieżność.

Rozpocznij próbną rozmowę JavaKarta płatnicza nie jest wymagana. Dostępna 1 darmowa sesja.
Ćwiczenie rozmów technicznych po angielskuTryb dla osób, dla których angielski nie jest językiem ojczystym, do ćwiczenia rozmów technicznych.

Java i JVM

1Czym jest Java i dlaczego uważa się ją za niezależną od platformy?

Java to statycznie typowany język ogólnego przeznaczenia. Kod źródłowy jest kompilowany do kodu bajtowego (bytecode), a kod bajtowy jest uruchamiany na maszynie wirtualnej JVM (Java Virtual Machine). Ten sam plik .class lub .jar zazwyczaj może zostać uruchomiony na różnych systemach operacyjnych bez konieczności ponownej kompilacji, jeśli dostępna jest kompatybilna JVM. Nie jest to absolutna niezależność: biblioteki natywne, ścieżki specyficzne dla danej platformy, polecenia systemowe i zachowanie systemu operacyjnego wciąż mogą sprawić, że aplikacja będzie zależna od konkretnej platformy.

Java source -> javac -> bytecode -> JVM -> machine code
Odpowiedz na to pytanie z trenerem AI

2Jaka jest różnica pomiędzy JVM (Java Virtual Machine), JRE (Java Runtime Environment) oraz JDK (Java Development Kit)?

JVM (Java Virtual Machine) ładuje i wykonuje kod bajtowy (bytecode) oraz zajmuje się zarządzaniem pamięcią, wątkami oraz odśmiecaniem pamięci (GC, Garbage Collector). JRE (Java Runtime Environment) historycznie określało środowisko JVM wzbogacone o biblioteki konieczne do uruchamiania aplikacji. Z kolei JDK (Java Development Kit) zawiera w sobie całe to środowisko uruchomieniowe i dodatkowo udostępnia narzędzia deweloperskie takie jak javac, java, debugger, javadoc, jar oraz narzędzia diagnostyczne. W nowoczesnych dystrybucjach osobne korzystanie z JRE jest rzadszym zjawiskiem, ponieważ obecnie aplikacje bywają zazwyczaj dostarczane wraz z wyspecjalizowanym i skrojonym na miarę obrazem środowiska uruchomieniowego.

JDK = development tools + runtime
Runtime = JVM + standard libraries
Odpowiedz na to pytanie z trenerem AI

3W jaki sposób JVM (Java Virtual Machine) wykonuje kod języka Java i dlaczego kompilacja JIT (Just-In-Time) jest potrzebna?

`javac` kompiluje kod źródłowy do kodu bajtowego. JVM ładuje klasy i zaczyna interpretować lub wykonywać instrukcje kodu bajtowego. Często wykonywane fragmenty kodu krytyczne dla wydajności są wykrywane w czasie działania programu i kompilowane przez kompilator JIT do zoptymalizowanego kodu maszynowego. JIT potrafi rozwijać metody (inlining), optymalizować pętle, stosować analizę ucieczki (escape analysis) i eliminować niektóre alokacje pamięci. To dlatego aplikacje napisane w Javie mogą działać szybciej po fazie rozgrzewki (warm-up), a mikrotesty wydajnościowe bez użycia narzędzia JMH często bywają mylące.

Odpowiedz na to pytanie z trenerem AI

4Jak działa ClassLoader?

`ClassLoader` ładuje definicje klas do JVM (Java Virtual Machine). Najbardziej powszechne programy ładujące to Bootstrap, Platform, Application oraz niestandardowe programy ładujące. Klasa jest identyfikowana nie tylko przez swoją w pełni kwalifikowaną nazwę, ale także przez `ClassLoader`, który ją załadował, dlatego dwie klasy o tej samej nazwie pochodzące z różnych programów ładujących mogą stanowić różne typy. Java zazwyczaj stosuje delegację do rodzica (parent delegation): program ładujący najpierw pyta swojego rodzica, aby zmniejszyć ryzyko zastąpienia klas platformy.

Loading
Linking: verification, preparation, resolution
Initialization
Odpowiedz na to pytanie z trenerem AI

5Jak zorganizowana jest pamięć JVM (Java Virtual Machine)?

Kluczowe obszary pamięci JVM (Java Virtual Machine) to sterta (heap) dla obiektów i tablic, stosy Javy (Java stacks) z ramkami wywołań (call frames) przypisanymi dla każdego wątku, Metaspace na metadane klas, pamięć podręczna kodu (code cache) na kod skompilowany metodą JIT (Just-In-Time compilation), rejestr PC (Program Counter) oraz stosy natywne. Należy unikać nadmiernego upraszczania, jakoby typy proste (primitives) zawsze znajdowały się na stosie, a obiekty zawsze na stercie: np. pole typu prostego będzie przetrzymywane wewnątrz swojego macierzystego obiektu na stercie, ponadto wbudowana analiza ucieczki (escape analysis) w kompilatorze JIT może pozwolić na całkowite usunięcie alokacji lub zmianę sposobu reprezentacji obiektu.

Odpowiedz na to pytanie z trenerem AI

6Jak działa GC (Garbage Collector)?

GC (Garbage Collector) zwalnia pamięć zajmowaną przez obiekty, które nie są już osiągalne z tak zwanych korzeni (GC Roots). Korzenie te obejmują zmienne lokalne aktywnych metod, pola statyczne, aktywne wątki oraz referencje JNI (Java Native Interface). Wiele mechanizmów odśmiecania opiera się na obserwacji, że większość nowych obiektów szybko staje się niepotrzebna, dlatego sterta (heap) jest często dzielona na młodą i starą generację (young and old generations). GC nie zamyka deterministycznie plików ani połączeń; zasoby zewnętrzne powinny być zamykane za pomocą konstrukcji try-with-resources.

try (var stream = Files.newInputStream(path)) {
    // use stream
}
Odpowiedz na to pytanie z trenerem AI

Programowanie obiektowe (OOP) i typy

7Jakie cztery zasady OOP (Object-Oriented Programming) wspiera Java?

Java wspiera enkapsulację, abstrakcję, dziedziczenie i polimorfizm. Enkapsulacja ukrywa stan wewnętrzny za publicznym API (Application Programming Interface). Abstrakcja skupia się na istotnym zachowaniu. Dziedziczenie tworzy nowy typ na podstawie istniejącego typu. Polimorfizm pozwala kodowi współpracować z różnymi implementacjami za pośrednictwem wspólnego kontraktu. W praktyce kompozycja jest często preferowana zamiast dziedziczenia, ponieważ tworzy mniejsze sprzężenie (lower coupling).

interface PaymentService {
    void pay(BigDecimal amount);
}

class CardPaymentService implements PaymentService {
    @Override
    public void pay(BigDecimal amount) {
        // payment implementation
    }
}
Odpowiedz na to pytanie z trenerem AI

8Czym różni się przeciążanie metod (overloading) od ich przesłaniania (overriding)?

Przeciążanie oznacza, że kilka metod ma tę samą nazwę, ale różne parametry; kompilator wybiera odpowiednią metodę na podstawie statycznych typów argumentów. Przesłanianie oznacza, że podklasa zastępuje implementację metody instancyjnej z klasy nadrzędnej; mechanizm dynamicznego wiązania w czasie działania programu (runtime dispatch) wybiera metodę na podstawie rzeczywistego typu obiektu. Metody statyczne są ukrywane, a nie przesłaniane, natomiast metody prywatne nie są przesłaniane, ponieważ nie są dziedziczone jako dostępne elementy klasy.

void send(String message) {}
void send(String message, int priority) {}

class Animal {
    void speak() { System.out.println("Animal"); }
}

class Dog extends Animal {
    @Override
    void speak() { System.out.println("Dog"); }
}
Odpowiedz na to pytanie z trenerem AI

9Jaka jest różnica między interfejsem a klasą abstrakcyjną?

Interfejs definiuje kontrakt i może zawierać metody abstrakcyjne, metody domyślne, metody statyczne, stałe oraz prywatne metody pomocnicze. Klasa abstrakcyjna może dodatkowo posiadać stan instancji, konstruktory, chronione pola, współdzieloną implementację oraz metody abstrakcyjne lub konkretne. Klasa może rozszerzać (dziedziczyć) tylko jedną klasę, ale może implementować wiele interfejsów. Używaj interfejsu dla niezależnych kontraktów, a klasy abstrakcyjnej dla blisko powiązanych typów ze współdzieloną implementacją i stanem.

interface Repository<T> {
    Optional<T> findById(long id);
}
Odpowiedz na to pytanie z trenerem AI

10Co oznacza słowo kluczowe final?

Zmiennej oznaczonej jako final można przypisać wartość tylko raz, ale referencja final nie sprawia, że wskazywany obiekt staje się niemutowalny. Metody final nie można nadpisać. Klasy final nie można dziedziczyć. Słowo kluczowe final komunikuje intencje i ogranicza zmiany referencji lub dziedziczenie, ale samo w sobie nie zapewnia w pełni niemutowalnego modelu ani bezpieczeństwa wątkowego (thread safety).

final int maxAttempts = 3;

final List<String> names = new ArrayList<>();
names.add("Alice"); // allowed

public final void validate() {}

public final class Token {}
Odpowiedz na to pytanie z trenerem AI

11Czym są rekordy (`record`) w Javie?

Rekord (`record`) to zwięzła składnia dla niemutowalnych nośników danych. Kompilator generuje prywatne pola `final`, konstruktor, metody dostępowe oraz metody `equals`, `hashCode` i `toString`. Rekordy są przydatne jako obiekty DTO (Data Transfer Object), obiekty wartości (value objects) oraz typy zwracane przez metody. Nie zastępują one jednak każdej encji domenowej: walidację można dodać w kompaktowym konstruktorze, ale złożone, mutowalne zachowania, leniwe ładowanie (lazy loading) i modele oparte na dziedziczeniu zazwyczaj słabo pasują do rekordów.

public record User(long id, String name) {}

public record Order(List<String> items) {
    public Order {
        items = List.copyOf(items);
    }
}
Odpowiedz na to pytanie z trenerem AI

12Jak utworzyć niemutowalny obiekt w Javie?

Uczyń klasę `final` lub ostrożnie ogranicz dziedziczenie, zadeklaruj pola jako `private final`, unikaj metod ustawiających (setterów) i przeprowadzaj walidację w konstruktorze. W przypadku danych wejściowych podlegających modyfikacji (mutowalnych), twórz kopie defensywne, a przy zwracaniu przekazuj kopie lub niemodyfikowalne widoki. Głęboka niemutowalność wymaga, aby cały zagnieżdżony stan był również niemutowalny. Pola `final` pomagają w bezpiecznej publikacji (safe publication), ale obiekt nadal może być logicznie mutowalny, jeśli eksponuje mutowalną kolekcję lub zagnieżdżony mutowalny obiekt.

public final class User {
    private final String name;
    private final List<String> roles;

    public User(String name, List<String> roles) {
        this.name = name;
        this.roles = List.copyOf(roles);
    }

    public List<String> getRoles() {
        return roles;
    }
}
Odpowiedz na to pytanie z trenerem AI

Referencje, ciągi znaków i porównywanie

13Czy w Javie parametry są przekazywane przez wartość, czy przez referencję?

Java zawsze przekazuje argumenty przez wartość. W przypadku typów prostych (primitives) kopiowana jest sama wartość. W przypadku obiektów kopiowana jest wartość referencji. Metoda może modyfikować obiekt poprzez skopiowaną referencję, ale nie może zastąpić referencji po stronie wywołującego innym obiektem. To rozróżnienie wyjaśnia, dlaczego zmiana pola może być widoczna dla wywołującego, podczas gdy przypisanie do parametru nowego obiektu nie jest.

void change(int value) {
    value = 100;
}

void rename(User user) {
    user.setName("Kate");
}

void replace(User user) {
    user = new User("New");
}
Odpowiedz na to pytanie z trenerem AI

14Jaka jest różnica między operatorami `==` a metodą `equals()`?

Dla typów prostych (primitives), `==` porównuje wartości. Dla referencji do obiektów, `==` sprawdza, czy obie referencje wskazują na ten sam obiekt. `equals()` jest metodą, która powinna porównywać równość logiczną, jeśli klasa implementuje ją poprawnie. Klasy takie jak `String`, `Integer`, `LocalDate` oraz wiele innych klas wartościowych nadpisuje metodę `equals`. W przypadku niestandardowych obiektów domenowych, metoda `equals` musi odzwierciedlać zamierzoną semantykę tożsamości lub wartości.

int a = 10;
int b = 10;
System.out.println(a == b); // true

String first = new String("java");
String second = new String("java");

System.out.println(first == second);      // false
System.out.println(first.equals(second)); // true
Odpowiedz na to pytanie z trenerem AI

15Czym jest kontrakt metod `equals` i `hashCode`?

Jeśli dwa obiekty są sobie równe według metody `equals`, muszą zwracać tę samą wartość z metody `hashCode`. Odwrotność tego twierdzenia nie jest wymagana: różne obiekty mogą mieć ten sam `hashCode`. Metoda `equals` powinna być zwrotna, symetryczna, przechodnia, spójna i dla wartości `null` zawsze zwracać `false`. Złamanie tego kontraktu uszkadza prawidłowe działanie klas `HashMap`, `HashSet` i innych kolekcji opartych na tablicach mieszających (hash-based collections): odnalezienie obiektu w kolekcji może stać się niemożliwe, nawet jeśli faktycznie się w niej znajduje.

@Override
public boolean equals(Object object) {
    if (this == object) return true;
    if (!(object instanceof User other)) return false;
    return id == other.id;
}

@Override
public int hashCode() {
    return Long.hashCode(id);
}
Odpowiedz na to pytanie z trenerem AI

16Dlaczego klasa String jest niemutowalna?

Klasa String jest niemutowalna ze względów bezpieczeństwa, przewidywalności oraz wydajności. Niemutowalność umożliwia proces internowania ciągów znaków (string interning), bezpieczne współdzielenie pomiędzy wątkami, niezawodne buforowanie wartości hashCode oraz bezpieczne użycie w ścieżkach, adresach URL (Uniform Resource Locator), nazwach klas i wartościach wrażliwych z punktu widzenia bezpieczeństwa. Operacje takie jak concat, replace i substring tworzą nowe ciągi znaków. Od wersji Java 9, klasa String może wewnętrznie używać tablicy byte[] ze zwięzłą reprezentacją (compact representation), ale publiczny kontrakt niemutowalności pozostaje taki sam.

String value = "Java";
value.toUpperCase();

System.out.println(value); // Java

String a = "java";
String b = "java";

System.out.println(a == b); // often true due to interning
Odpowiedz na to pytanie z trenerem AI

17Jaka jest różnica pomiędzy klasami String, StringBuilder i StringBuffer?

Klasa String jest niemutowalna, więc wielokrotna konkatenacja w pętlach może tworzyć wiele obiektów pośrednich, chociaż kompilator potrafi optymalizować proste przypadki. Klasa StringBuilder jest mutowalna i preferowana do budowania ciągów znaków w pojedynczym wątku. Klasa StringBuffer działa podobnie, ale jest synchronizowana, przez co jest zazwyczaj wolniejsza i ma zastosowanie głównie w przestarzałych (legacy), bezpiecznych dla wątków (thread-safe) interfejsach API (Application Programming Interface). Do formatowania użycie String.format lub interfejsów API opartych na szablonach może być bardziej czytelne, ale nie zawsze jest szybsze.

StringBuilder builder = new StringBuilder();

for (String item : items) {
    builder.append(item).append(',');
}

String result = builder.toString();

String message = "Hello, " + name;
Odpowiedz na to pytanie z trenerem AI

Typy generyczne i kolekcje

18Czym są typy generyczne i czym jest wymazywanie typów?

Typy generyczne pozwalają kodowi na wyrażanie bezpiecznych typologicznie kontenerów i API, takich jak `List<String>`. W czasie kompilacji kompilator sprawdza typy i wstawia rzutowania tam, gdzie to konieczne. W czasie wykonywania, większość informacji o typach generycznych jest wymazywana, więc `List<String>` i `List<Integer>` współdzielą tę samą klasę w czasie wykonywania. Z tego powodu nie można bezpośrednio utworzyć `new T()`, używać typów prostych jako parametrów typu, ani niezawodnie sprawdzić `instanceof List<String>`.

List<String> names = new ArrayList<>();
names.add("Alice");

List<? extends Number> producer;
List<? super Integer> consumer;
Odpowiedz na to pytanie z trenerem AI

19Czym jest Java Collections Framework?

Collections Framework to zestaw interfejsów i implementacji do przechowywania oraz przetwarzania grup obiektów. Główne interfejsy to `List`, `Set`, `Queue`, `Deque` i `Map`. Implementacje obejmują `ArrayList`, `LinkedList`, `HashSet`, `TreeSet`, `HashMap`, `TreeMap` i `PriorityQueue`. Kolekcję należy dobierać na podstawie wzorca dostępu, wymagań dotyczących kolejności, unikalności elementów, złożoności wyszukiwania, narzutu pamięciowego i wymagań w zakresie współbieżności.

List<String> list = new ArrayList<>();
Set<Long> ids = new HashSet<>();
Map<Long, User> usersById = new HashMap<>();
Deque<String> queue = new ArrayDeque<>();
Odpowiedz na to pytanie z trenerem AI

20Jak działa `HashMap`?

`HashMap` przechowuje wpisy w wiadrach (buckets), które są wybierane na podstawie skrótu (hash) klucza. Podczas wyszukiwania oblicza skrót, znajduje odpowiednie wiadro i porównuje klucze za pomocą metody `equals`. Kolizje są obsługiwane wewnątrz wiadra; we współczesnej Javie długie łańcuchy mogą zostać przekształcone w zrównoważone drzewa (treeified), gdy spełnione są odpowiednie warunki. `HashMap` dopuszcza jeden klucz o wartości null i wartości null, nie jest bezpieczna wątkowo (thread-safe) i może zmienić swój rozmiar, gdy przekroczony zostanie próg współczynnika obciążenia (load factor).

Map<Long, User> users = new HashMap<>();
users.put(user.id(), user);

User found = users.get(user.id());
Odpowiedz na to pytanie z trenerem AI

21Jaka jest różnica między HashMap, LinkedHashMap, TreeMap i ConcurrentHashMap?

HashMap zapewnia szybki, nieuporządkowany dostęp do danych i nie jest bezpieczna wątkowo (thread-safe). LinkedHashMap zachowuje kolejność wstawiania lub dostępu i jest użyteczna przy tworzeniu pamięci podręcznych typu LRU (Least Recently Used). TreeMap utrzymuje klucze posortowane według porządku naturalnego lub za pomocą obiektu Comparator i wykonuje operacje w czasie O(log n). ConcurrentHashMap jest zaprojektowana z myślą o dostępie współbieżnym przy wykorzystaniu synchronizacji segmentowej lub wewnętrznej, nie pozwala na wartości pustych (null) kluczy ani wartości, i przy dużym obciążeniu jest preferowana zamiast synchronizedMap.

map.computeIfAbsent(key, ignored -> loadValue());
Odpowiedz na to pytanie z trenerem AI

22Jaka jest różnica między ArrayList i LinkedList?

ArrayList jest oparta na tablicy. Zapewnia szybki dostęp indeksowany, zwarty układ pamięci i wydajną iterację, ale wstawianie elementów w środku wymaga ich przesuwania. LinkedList to lista podwójnie wiązana. Wstawianie jest w niej tańsze, jeśli pożądany węzeł jest już znany, jednak dostęp indeksowany wymaga czasu O(n), narzut pamięciowy jest wyższy, a lokalność w pamięci podręcznej (cache locality) jest gorsza. W wielu rzeczywistych zastosowaniach ArrayList jest szybsza, nawet jeśli występują w nich operacje wstawiania elementów.

Odpowiedz na to pytanie z trenerem AI

23Jaka jest różnica między Comparable a Comparator?

Comparable definiuje naturalny porządek wewnątrz klasy za pomocą metody compareTo. Comparator to oddzielny obiekt lub wyrażenie lambda, które definiuje sposób sortowania z zewnątrz. Comparable przydaje się, gdy dany typ posiada jeden, oczywisty porządek domyślny. Comparator sprawdza się lepiej, gdy istnieje wiele kryteriów sortowania, na przykład według nazwy, daty lub priorytetu. Metody compare i compareTo muszą być spójne i najlepiej kompatybilne z metodą equals, gdy są używane w posortowanych zbiorach lub mapach.

class User implements Comparable<User> {
    private int age;

    @Override
    public int compareTo(User other) {
        return Integer.compare(age, other.age);
    }
}

Comparator<User> byName =
    Comparator.comparing(User::getName);

Comparator<User> byAgeThenName =
    Comparator.comparingInt(User::getAge)
              .thenComparing(User::getName);
Odpowiedz na to pytanie z trenerem AI

Wyjątki i zasoby

24Jaka jest różnica między wyjątkami sprawdzanymi (checked exceptions) i niesprawdzanymi (unchecked exceptions)?

Wyjątki sprawdzane dziedziczą po klasie Exception, ale nie po RuntimeException, a kompilator wymaga od metod wywołujących ich obsłużenia lub zadeklarowania. Wyjątki niesprawdzane dziedziczą po klasie RuntimeException i nie wymagają ich deklaracji w sygnaturze metod. Wyjątki sprawdzane pasują do sytuacji, w których możliwy jest powrót do normalnego działania, występujących na granicach API (Application Programming Interface). Wyjątki niesprawdzane sprawdzają się w przypadku błędów programistycznych, nieprawidłowego stanu i naruszeń kontraktu. W nowoczesnym kodzie usługowym zespoły często unikają nadużywania wyjątków sprawdzanych, ponieważ nadmiernie zanieczyszczają one kolejne warstwy aplikacji.

void load() throws IOException {
    // ...
}

throw new IllegalArgumentException("Invalid id");
Odpowiedz na to pytanie z trenerem AI

25Jaka jest różnica między throw, throws i try-with-resources?

Słowo kluczowe throw natychmiast zgłasza wyjątek. throws deklaruje możliwość wystąpienia wyjątku w sygnaturze metody. Konstrukcja try-with-resources automatycznie zamyka zasoby implementujące interfejs AutoCloseable w odwrotnej kolejności. Jeśli zarówno główny blok kodu, jak i metoda zamykająca zasób wyrzucą wyjątki, ten drugi zostanie zachowany jako wyjątek stłumiony (suppressed exception). Jest to bezpieczniejsze i dużo czytelniejsze rozwiązanie niż ręczne używanie bloków finally przy zarządzaniu kilkoma zasobami.

throw new IllegalArgumentException("Invalid id");

void readFile() throws IOException {}

try (var reader = Files.newBufferedReader(path)) {
    return reader.readLine();
}
Odpowiedz na to pytanie z trenerem AI

Stream API i styl funkcyjny

26Czym jest Stream API?

Stream API (Application Programming Interface) pozwala na deklaratywne przetwarzanie sekwencji danych. Operacje pośrednie (intermediate operations), takie jak filter, map i sorted, są leniwe (lazy); operacje końcowe (terminal operations), takie jak toList, collect, reduce i forEach, powodują właściwe wykonanie ciągu poleceń. Strumień nie jest kolekcją, zazwyczaj używa się go tylko raz i najlepiej sprawdza się przy pracy z czystymi funkcjami (pure functions), wolnymi od skutków ubocznych. Metoda parallelStream nie gwarantuje szybszego działania programu, a jej użycie może zaszkodzić wydajności i poprawności.

List<String> names = users.stream()
    .filter(User::isActive)
    .sorted(Comparator.comparing(User::getName))
    .map(User::getName)
    .toList();
Odpowiedz na to pytanie z trenerem AI

27Czym jest Optional i kiedy powinno się go używać?

Klasa Optional<T> w jednoznaczny sposób reprezentuje brak wyniku i doskonale sprawdza się jako typ zwracany (return type). Do przydatnych metod należą map, flatMap, filter, orElse, orElseGet oraz orElseThrow. Metoda orElse wylicza (ewaluuje) swój argument w sposób natychmiastowy (zachłanny), podczas gdy funkcja dostarczająca wynik (supplier) w orElseGet zostaje uruchomiona dopiero wtedy, gdy pożądana wartość jest nieobecna. Zazwyczaj nie poleca się używania klasy Optional jako pól encji, w argumentach metod czy w serializowanych obiektach typu DTO (Data Transfer Object), chyba że jest ku temu wyraźny, uzasadniony powód.

Optional<User> user = repository.findById(id);

String name = user
    .map(User::getName)
    .orElse("Unknown");

optional.orElse(expensiveOperation());
optional.orElseGet(() -> expensiveOperation());
Odpowiedz na to pytanie z trenerem AI

Współbieżność

28Co gwarantują modyfikatory synchronized, volatile oraz klasy atomowe?

Słowo kluczowe synchronized zapewnia obustronne wykluczanie oraz istnienie relacji poprzedzania (happens-before relationship). Modyfikator volatile gwarantuje widoczność najbardziej aktualnych zapisów i ogranicza zmianę kolejności wykonywania instrukcji (reordering), natomiast nie powoduje, że złożone operacje stają się w 100% atomowe. Klasy atomowe udostępniają pojedyncze operacje tego typu, np. metodę incrementAndGet. Używaj blokad bądź słowa kluczowego synchronized w przypadku kilku powiązanych ze sobą zmian stanu, volatile przy zwykłych flagach i AtomicInteger lub LongAdder przy dużej rywalizacji o dostęp (contention).

synchronized (lock) {
    counter++;
}

private volatile boolean running = true;

AtomicInteger counter = new AtomicInteger();
counter.incrementAndGet();
Odpowiedz na to pytanie z trenerem AI

29Czym są wyścig (race condition), zakleszczenie (deadlock) i bezpieczeństwo wątkowe (thread safety)?

Zjawisko wyścigu (race condition) występuje, gdy wynik operacji uzależniony jest od nieprzewidywalnej kolejności, w jakiej wykonywane są jej wątki. Zakleszczenie (deadlock) z kolei to sytuacja, w której wątki w nieskończoność oczekują na przejęcie cudzych, wzajemnie zablokowanych zasobów. Aby zaprogramować proces w sposób bezpieczny wątkowo (thread safe), zredukuj do minimum współdzielony, modyfikowalny stan programu (shared mutable state), zachowaj spójną kolejność zakładania blokad, staraj się jak najbardziej skrócić sekcje krytyczne, zapobiegaj wywoływaniu zewnętrznego kodu od wewnątrz blokad, używaj obiektów niezmiennych (immutable objects), kolekcji wspierających współbieżność, limitów czasu (timeouts) oraz mechanizmów przekazywania komunikatów (message passing). Bezpieczeństwo wątkowe to w rzeczywistości cecha obejmująca całkowity protokół służący zarządzaniu dostępem do stanu.

Odpowiedz na to pytanie z trenerem AI

30Jaka jest różnica między ExecutorService, CompletableFuture a wątkami wirtualnymi (virtual threads)?

ExecutorService to interfejs odpowiedzialny za administrowanie pulą wątków i kontrolę wykonania zadań. CompletableFuture ułatwia zarządzanie operacjami asynchronicznymi. Wątki wirtualne z kolei są tzw. lekkimi wątkami dostępnymi w ramach wirtualnej maszyny Java JVM (Java Virtual Machine), które pozwalają ułatwić zarządzanie dużą ilością blokujących operacji wejścia-wyjścia I/O (Input/Output). Upraszczają one na przykład zaimplementowanie modelu „jeden wątek na jedno żądanie” (thread-per-request), lecz jednocześnie ani trochę nie skracają operacji obciążających bezpośrednio procesor z zadaniami obliczeniowymi (CPU-bound work), nie niwelują restrykcji i limitów ze strony używanej bazy danych lub innych zasobów połączonych zewnętrznie, a także nie powodują, że używany w programie podatny na jakiekolwiek edycje modyfikowalny stan programu samoistnie w jakikolwiek sposób stałby się bezpieczny wątkowo.

try (var executor = Executors.newFixedThreadPool(4)) {
    Future<Integer> future = executor.submit(() -> calculate());
    int result = future.get();
}

CompletableFuture<User> userFuture =
    CompletableFuture.supplyAsync(this::loadUser)
        .thenApply(this::enrichUser)
        .exceptionally(error -> fallbackUser());

try (var executor =
         Executors.newVirtualThreadPerTaskExecutor()) {
    executor.submit(() -> handleRequest());
}
Odpowiedz na to pytanie z trenerem AI