Demo 1 (20-25 min): Wyścig danych i poprawki
Cel: pokazać, że brak synchronizacji daje błędne wyniki.
Kroki:
- Utwórz licznik bez synchronizacji.
- Uruchom 4-8 wątków, każdy wykonuje np. 100_000 inkrementacji.
- Porównaj wynik oczekiwany i rzeczywisty.
- Napraw wersją
synchronized. - Napraw wersją
AtomicIntegeri porównaj czytelność.
Szkielet:
int threads = 8; int increments = 100_000; // uruchomienie wielu wątków i join // wypisanie: expected = threads * increments
Demo 1 - wskazówki i rozwiązanie
Wskazówki:
- Zadbaj o
join()dla wszystkich wątków. - Ten sam obiekt licznika musi być współdzielony.
Wersja poprawna (AtomicInteger):
import java.util.concurrent.atomic.AtomicInteger; class AtomicCounter { private final AtomicInteger value = new AtomicInteger(); void inc() { value.incrementAndGet(); } int get() { return value.get(); } }
Demo 2 (20-25 min): Producent-konsument
Cel: komunikacja wait/notifyAll na monitorze.
Zakres:
- Bufor 1-elementowy.
- Producent tworzy liczby 1..N.
- Konsument odczytuje i wypisuje.
- Bez aktywnego oczekiwania (bez pętli z
sleepjako mechanizmu synchronizacji).
Wskazówki:
put()itake()jakosynchronized.- Warunki pilnuj przez
while.
Demo 2 - rozwiązanie (skrót)
OneSlotBuffer buffer = new OneSlotBuffer(); Thread producer = new Thread(() -> { for (int i = 1; i <= 10; i++) { try { buffer.put(i); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); Thread consumer = new Thread(() -> { for (int i = 1; i <= 10; i++) { try { System.out.println(buffer.take()); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } }); producer.start(); consumer.start();
Zadania samodzielne (20-25 min) - wybór
Wybierz jedno zadanie. Każde zawiera wersję podstawową i rozszerzenie.
Zadanie A (łatwe): Licznik wielowątkowy
Wymagania:
- Zaimplementuj
Counteri uruchom 6 wątków po 50_000 inkrementacji. - Pokaż wersję błędną (bez synchronizacji) oraz poprawną.
- Dodaj pomiar czasu
System.nanoTime().
Wskazówki:
- Użyj
join(). - W wersji poprawnej zastosuj
AtomicIntegeralbosynchronized.
Rozwiązanie (minimum):
// expected = 300_000 // brokenCounter -> zwykle mniej // fixedCounter -> dokładnie 300_000
Zadanie B (średnie): Suma tablicy w wielu wątkach
Wymagania:
- Wygeneruj tablicę
int[]. - Policz sumę jednowątkowo i wielowątkowo (podział na fragmenty).
- Porównaj wyniki i czas.
Wskazówki:
- Każdy wątek liczy lokalną sumę fragmentu.
- Wyniki zsumuj po
join(). - Alternatywa:
ExecutorService+Callable<Long>.
Rozwiązanie (minimum):
// podziel [0, n) na k przedziałów // submit Callable<Long> // sumuj wyniki z Future.get()
Zadanie C (trudniejsze): Ograniczony downloader
Wymagania:
- Symuluj pobieranie wielu plików (
Thread.sleep(…)). - Maksymalnie 3 pobrania równocześnie.
- Wypisuj start/koniec zadania i nazwę wątku.
Wskazówki:
- Użyj
Semaphore(3)alboExecutors.newFixedThreadPool(3). - Pamiętaj o zwalnianiu zasobu w
finally.
Rozwiązanie (minimum):
ExecutorService pool = Executors.newFixedThreadPool(3); for (String file : files) { pool.submit(() -> { System.out.println("START " + file + " " + Thread.currentThread().getName()); Thread.sleep(500); System.out.println("END " + file + " " + Thread.currentThread().getName()); }); } pool.shutdown();
Zadanie D (dla chętnych): Deadlock i naprawa
Wymagania:
- Napisz przykład deadlocka na 2 blokadach.
- Następnie napraw go przez stałą kolejność blokad.
Wskazówki:
- Dwa obiekty:
lockA,lockB. - Wersja błędna: różna kolejność w dwóch wątkach.
- Wersja poprawna: zawsze najpierw
lockA, potemlockB.
Zadanie PA
Zadanie 4.1
Zaimplementuj klasę, która wykorzystując wątki pozwoli na podanie sumy wszystkich elementów w tablicach wygenerowanych losowo. Użytkownik ma mieć możliwość określenia długości tablic i ich liczby.
Zadanie 4.2
Zaimplementuj klasę statyczną, która z wykorzystaniem wielu wątków pozwoli wykonywanie mnożenia dwóch macierzy przechowywanych w postaci tablic dwuwymiarowych. Zadbaj o to, aby przed rozpoczęciem operacji sprawdzić, czy mnożenie macierzy można w ogóle wykonać.
Zadanie 4.3
Zaimplementuj klasę statyczną, która pozwoli z wykorzystaniem wielu wątków doprowadzić macierz przechowywaną w postaci tablicy dwuwymiarowej do postaci górnotrójkątnej (eliminacja Gaussa). Dodatkowe 3 pkt. otrzymać można za doprowadzenie macierzy do postaci zdiagonalizowanej. Przed wykonaniem operacji sprawdź, czy spełnione są warunki niezbędne do tego, aby operacja się powiodła.