Celem laboratorium jest zapoznanie się z wątkami i innymi narzędziami do obsługi współbieżnych operacji w Javie.
Najważniejsze zadania:
- Stworzenie
SimulationEngine
, który pozwoli uruchamiać symulacje asynchronicznie. - Rozwiązanie problemu z race condition.
- Zastosowanie puli wątków do efektywnego zarządzania symulacjami.
- Uruchomienie wielu symulacji jednocześnie sprawi, że ciężko będzie odróżnić na konsoli wyniki wypisywane przez nich wyniki. Dlatego konieczne jest dodanie do modelu mapy unikalnego identyfikatora - w interfejsie
WorldMap
zadeklaruj metodęgetId()
(może zwracaćint
,String
lub - jeśli chcemy być bardzo profesjonalni -UUID
). Dostarcz również konieczną implementację. Pamiętaj o zmodyfikowaniu konstruktorów obu map i zastanów się, gdzie umieścić atrybut tak, by nie powtarzać kodu. - W
ConsoleMapDisplay
dodaj wypisywanie id mapy. - Stwórz klasę
SimulationEngine
. Powinna ona w konstruktorze przyjmować listę obiektówSimulation
. - W utworzonej klasie zdefiniuj metodę
runSync()
, która uruchomi po kolei wszystkie symulacje. - Sprawdź działanie programu dla dwóch symulacji (jednej dla
RectangularMap
, drugiej dlaGrassField
). Możesz użyć parametry symulacji z poprzednich laboratoriów (dobrze by lista ruchów miała przynajmniej kilkanaście pozycji).
-
Dodaj do
SimulationEngine
metodęrunAsync()
. Metoda powinna:- Stworzyć osobny wątek dla każdego obiektu
Simulation
. - Uruchomić stworzony wątek.
Uwaga: Co należy zrobić by klasa
Thread
mogła przyjąć w konstruktorze obiektSimulation
? - Stworzyć osobny wątek dla każdego obiektu
-
Ponownie przetestuj działanie programu na dwóch symulacjach, tym razem dla metody
runAsync()
. Zwróć uwagę na kolejność wypisywanych aktualizacji mapy - czy różni się ona od efektu wywołaniarunSync()
? -
Zwróć uwagę, w którym miejscu pojawia się teraz napis
System zakończył działanie
(w razie potrzeby dodaj wypisywanie takiego napisu w ostatniej liniimain()
). -
Dodaj do klasy
SimulationEngine
metodęawaitSimulationsEnd()
i zastanów się, w jaki sposób zmodyfikować przygotowany mechanizm wątków tak, by wywołanie tej metody blokowało wątek, z którego została wywołana do momentu zakończenia wszystkich symulacji.
- Zmodyfikuj metodę
main()
tak by zamiast dwóch symulacji tworzyła ich bardzo wiele w pętli (rzędu 1000 lub więcej - możesz eksperymentować z różnymi wartościami). - Uruchom program kilka razy i prześledź wypisywane stany mapy oraz w szczególności końcowe wartości liczników aktualizacji. Jakie problemy obserwujesz? Z czego to wynika?
- Dokonaj koniecznych modyfikacji w programie tak by zapewnić, że każda aktualizacja mapy zostanie poprawnie i w całości wypisana.
- W
SimulationEngine
napisz dodatkową metodęrunAsyncInThreadPool()
. - Zaimplementuj metodę tak by symulacje (niezależnie od ich liczby) były uruchamiane w puli 4 wątków. Skorzystaj z
ExecutorService
i pomocniczej klasyExecutors
. - Zmodyfikuj metodę
awaitSimulationsEnd()
tak by kończyła również działanie puli wątków i oczekiwała na zakończenie jej pracy maksymalnie 10 sekund. - Uruchom program i sprawdź czy działa. Warto też porównać zużycie procesora w wariancie z pulą i bez niej. Dlaczego zastosowanie puli wątków jest lepszym pomysłem niż poprzednie podejście?
-
Mechanizm wątków służy do realizacji zadań, które powinny być realizowane współbieżnie, a jeśli system posiada wiele procesorów, to również równolegle. Wykonujące wątki nawzajem nie blokują swego wykonania.
-
Przykładami takich operacji może być np. dostęp do zasobu sieciowego, uruchamianie ciężkich obliczeń albo logika wykonywana w celu późniejszego zaktualizowania interfejsu graficznego.
-
Domyślnie aplikacja posiada jeden wątek, który może stworzyć dowolnie wiele dodatkowych.
-
Aby stworzyć wątek możemy skorzystać z klasy
Thread
i interfejsuRunnable
.class SimulationEngine implements Runnable { @Override public void run() { System.out.println("Thread started."); } } SimulationEngine engine = new SimulationEngine(); Thread engineThread = new Thread(engine); engineThread.start();
-
Metody w Javie mogą posiadać dodatkowy modyfikator
synchronized
. Gwarantuje on, że dana metoda zostanie zawsze wywołana w całości przez dany wątek, zanim zostanie do niej dopuszczony inny, współbieżnie pracujący wątek. -
Słowo
synchronized
występuje również w formie bloku, pozwalającego wydzielać tzw. sekcje krytyczne jako fragment metody, np:// kod wywoływany współbieżnie synchronized(this) { // sekcja krytyczna - tylko jeden wątek naraz tu wejdzie } // kod wywoływany współbieżnie
-
Zarządzanie wątkami ręcznie nie jest efektywne i wygodne. W profesjonalnych zastosowaniach lepiej stosować narzędzia, które robią to za nas, takie jak pule wątków. W Javie do zarządzania pulami służy
ExecutorService
:ExecutorService executorService = Executors.newFixedThreadPool(8); executorService.submit(runnable); // task runanble wywoła się na jednym z 8 wątków lub poczeka, aż któryś się zwolni