PW 2026L · Kolos: 13.04.2026
Format: 2 zadania kodowania, ok. 20–25 pkt każde. Możesz używać komputera.
import java.util.concurrent.*; public class BonjourTask { public static void main(String[] args) throws Exception { ScheduledExecutorService executor = Executors.newScheduledThreadPool(1); final int[] count = {0}; executor.scheduleAtFixedRate(() -> { System.out.println("Bonjour"); count[0]++; if (count[0] >= 10) executor.shutdown(); }, 0, 1, TimeUnit.SECONDS); } }
sleep() traci precyzję bo nie uwzględnia czasu wykonania ciała pętli. scheduleAtFixedRate() uruchamia od momentu bazowego co dokładnie 1s.
public class MailBox { private boolean letterDelivered = false; private boolean letterReceived = false; // Listonosz: wkłada list, czeka na potwierdzenie public synchronized void deliver() throws InterruptedException { letterDelivered = true; notifyAll(); // budzi adresata while (!letterReceived) wait(); // czeka na potwierdzenie System.out.println("Listonosz: potwierdzono odbiór"); } // Adresat: czeka na list, potwierdza odbiór public synchronized void receive() throws InterruptedException { while (!letterDelivered) wait(); // czeka na list letterReceived = true; notifyAll(); // budzi listonosza System.out.println("Adresat: odebrałem list"); } }
import java.util.concurrent.locks.*; public class AClass { private final Lock lock = new ReentrantLock(); public void method1() { lock.lock(); try { System.out.println("instrukcja1"); } finally { lock.unlock(); // zawsze w finally! } } public void method2() { lock.lock(); try { System.out.println("instrukcja2"); } finally { lock.unlock(); } } }
Jeden zamek dla obu metod — tak jak synchronized na this, method1 i method2 wzajemnie się blokują. Dwa osobne zamki byłyby BŁĘDEM — metody mogłyby działać równolegle, co nie jest równoważne oryginałowi.
public class Main { public static void main(String[] args) { Thread[] ref = new Thread[2]; ref[0] = new Thread(() -> { try { Thread.sleep(1000); ref[1].interrupt(); // przerywa thread2 Thread.sleep(5000); // czeka — musi żyć żeby thread2 mógł go przerwać! } catch (InterruptedException e) { System.out.println("thread1 przerwany przez thread2"); } }); ref[1] = new Thread(() -> { try { Thread.sleep(2000); System.out.println("thread2: 2s upłynęły"); } catch (InterruptedException e) { System.out.println("thread2 przerwany przed 2s"); ref[0].interrupt(); // przerywa thread1 } }); ref[0].start(); ref[1].start(); } }
Kluczowe: thread1 po przerwaniu thread2 musi dalej czekać (sleep), żeby był jeszcze żywy kiedy thread2 spróbuje go przerwać. Bez drugiego sleep → thread1 jest TERMINATED i interrupt() nic nie robi.
public class MojWatek implements Runnable { @Override public void run() { System.out.println("Runnable"); } public static void main(String[] args) { new Thread(new MojWatek()).start(); } }
✅ Preferowane — pozwala dziedziczyć po innej klasie
public class MojWatek extends Thread { @Override public void run() { System.out.println("Thread"); } public static void main(String[] args) { new MojWatek().start(); } }
⚠️ Brak wielodziedziczenia w Javie!
new Thread(new Runnable() { @Override public void run() { System.out.println("anon"); } }).start();
new Thread( () -> System.out.println("lambda") ).start();
Najkrótsza forma!
new Thread(MyClass::myMethod).start();
Gdy metoda już istnieje
| Metoda | Opis | Uwaga |
|---|---|---|
start() | Uruchamia wątek (wywołuje run() w nowym wątku) | OK |
join() | Czeka na zakończenie wątku | OK |
sleep(ms) | Zawiesza bieżący wątek | OK |
isAlive() | Czy wątek jeszcze działa? | OK |
interrupt() | Wysyła sygnał przerwania | OK |
stop() | Zatrzymuje wątek | DEPRECATED |
suspend() | Zawiesza wątek | DEPRECATED |
resume() | Wznawia wątek | DEPRECATED |
Operacja a++ NIE jest atomowa — składa się z 3 kroków: odczyt → dodaj → zapis. Przy 2 wątkach może nastąpić wyścig i utrata danych.
volatile (w tym long/double)a++ NIE jest atomowa! Używaj AtomicInteger.
public synchronized void zwieksz() { wartosc++; }
Zamek na this. Tylko 1 wątek naraz w metodzie.
public void zwieksz() { synchronized(this) { wartosc++; } // reszta niesync }
Precyzyjniejsza kontrola. Lepszy performance.
Lock lock = new ReentrantLock(); lock.lock(); try { wartosc++; } finally { lock.unlock(); // ! }
| Metoda | Opis |
|---|---|
lock() | Blokuje (czeka jeśli zajęty) |
unlock() | Zwalnia zamek — ZAWSZE w finally! |
tryLock() | Próbuje zamek, zwraca boolean (nie blokuje) |
tryLock(t, unit) | Próbuje przez czas t |
lockInterruptibly() | Jak lock(), ale można przerwać sygnałem |
public synchronized void dodaj(int v) throws InterruptedException { while (queue.size() == MAX) wait(); queue.add(v); notifyAll(); } public synchronized int pobierz() throws InterruptedException { while (queue.isEmpty()) wait(); int v = queue.poll(); notifyAll(); return v; }
interrupt() — wysyła sygnał przerwania wątkowiisInterrupted() — sprawdza flagę (nie czyści)interrupted() — sprawdza i czyści flagę (statyczna)Metody blokujące (sleep, wait, join) rzucają InterruptedException i czyszczą flagę na false.
public void run() { while (true) { try { Thread.sleep(1000); } catch (InterruptedException ie) { // obsłuż przerwanie return; } } }
wait() — zwalnia zamek i czekanotify() — budzi jeden czekający wąteknotifyAll() — budzi wszystkie czekające wątki⚠️ Można wywoływać TYLKO wewnątrz synchronized!
public synchronized void guardedCondition() throws InterruptedException { while (!warunek) { wait(); // zawsze w pętli while! } // warunek spełniony } public synchronized void notifyCondition() { warunek = true; notifyAll(); }
Zawiesza bieżący wątek do momentu zakończenia innego wątku. Wewnętrznie używa bloku strzeżonego z wait().
// Thread t = ...; t.start(); t.join(); // czeka aż t się skończy System.out.println("t zakończony");
| sleep() | wait() | |
|---|---|---|
| Klasa | Thread (statyczna) | Object (instancja) |
| Zamek | NIE zwalnia zamka | Zwalnia zamek |
| Budzi | Po upływie czasu | notify() / notifyAll() |
| Kontekst | Wszędzie | Tylko w synchronized |
// Pula 4 wątków ExecutorService exec = Executors.newFixedThreadPool(4); // Callable zwraca wynik Callable<String> zadanie = () -> "wynik"; Future<String> future = exec.submit(zadanie); String wynik = future.get(); // blokuje exec.shutdown(); // zawsze!
ScheduledExecutorService sched = Executors.newScheduledThreadPool(1); // Jednorazowo po 5s sched.schedule(task, 5, TimeUnit.SECONDS); // Co 1s, pierwsze od razu sched.scheduleAtFixedRate( task, 0, 1, TimeUnit.SECONDS); // Co 1s od końca poprzedniego sched.scheduleWithFixedDelay( task, 0, 1, TimeUnit.SECONDS);
class SumTask extends RecursiveTask<Integer> { @Override protected Integer compute() { if (koniec - start < 10) { return /* oblicz sekwencyjnie */; } int mid = (start + koniec) / 2; SumTask t1 = new SumTask(tab, start, mid); SumTask t2 = new SumTask(tab, mid, koniec); invokeAll(t1, t2); return t1.join() + t2.join(); } } ForkJoinPool pool = new ForkJoinPool(); int wynik = pool.invoke(new SumTask(...));
RecursiveTask → zwraca wynik; RecursiveAction → bez wyniku
List<Integer> liczby = Arrays.asList(1,2,3,4,5); // Sekwencyjny int suma = liczby.stream() .mapToInt(Integer::intValue) .sum(); // Równoległy — wiele wątków int sumaR = liczby.parallelStream() .mapToInt(Integer::intValue) .sum();
| Interfejs/Klasa | Metoda kluczowa | Zwraca | Opis |
|---|---|---|---|
| Runnable | run() | void | Prosta operacja, brak wyniku |
| Callable<V> | call() | V | Operacja zwracająca wynik |
| Future<V> | get() | V | Pobiera wynik (blokuje) |
| RecursiveTask<V> | compute() | V | Fork-Join z wynikiem |
| RecursiveAction | compute() | void | Fork-Join bez wyniku |
Dwa wątki wzajemnie blokują się w nieskończoność — każdy czeka na zamek trzymany przez drugiego.
// A trzyma lock1, czeka na lock2 // B trzyma lock2, czeka na lock1 // → wieczna blokada
Wątek nie dostaje dostępu do zasobu, bo inne wątki są „zachłanne" i zajmują go nieustannie.
Np. metoda synchronized wykonuje się długo i jest często wywoływana przez inny wątek — reszta ciągle zablokowana (BLOCKED).
Używaj fair lock: new ReentrantLock(true)
Wątki reagują na siebie nawzajem i uniemożliwiają postęp — choć nie są zablokowane (aktywnie „działają").
Analogia: dwie osoby w wąskim korytarzu ustępują sobie w nieskończoność, żadna nie przejdzie.
Wprowadź losowe opóźnienie lub priorytety.
class Friend { public synchronized void bow(Friend bower) { System.out.println(name + ": " + bower.name + " ukłonił się"); bower.bowBack(this); // ← próbuje wejść w synchronized bowera! } public synchronized void bowBack(Friend bower) { ... } } // Thread1: alphonse.bow(gaston) → trzyma zamek alphonse, czeka na zamek gaston // Thread2: gaston.bow(alphonse) → trzyma zamek gaston, czeka na zamek alphonse // DEADLOCK