Java SE, Laboratorium nr 8 Wątki

Podobne dokumenty
Współbieżność i równoległość w środowiskach obiektowych. Krzysztof Banaś Obliczenia równoległe 1

Java. Wykład. Dariusz Wardowski, Katedra Analizy Nieliniowej, WMiI UŁ

Programowanie komputerów

Język Java wątki (streszczenie)

Współbieżność i równoległość w środowiskach obiektowych. Krzysztof Banaś Obliczenia równoległe 1

WSPÓŁBIEŻNOŚĆ. MATERIAŁY:

Autor: dr inż. Zofia Kruczkiewicz, Programowanie aplikacji internetowych 1

Współbieżność w środowisku Java

Kurs programowania. Wykład 8. Wojciech Macyna

Aplikacje w Javie- wykład 11 Wątki-podstawy

Wątki. Definiowanie wątków jako klas potomnych Thread. Nadpisanie metody run().

Kurs programowania. Wykład 8. Wojciech Macyna. 10 maj 2017

Obliczenia równoległe i rozproszone w JAVIE. Michał Kozłowski 30 listopada 2003

Java. Programowanie Obiektowe Mateusz Cicheński

Wątki w Javie. Piotr Tokarski

Język Java wątki (streszczenie)

Wielowątkowość. Programowanie w środowisku rozproszonym. Wykład 1.

1 Wątki 1. 2 Tworzenie wątków 1. 3 Synchronizacja 3. 4 Dodatki 3. 5 Algorytmy sortowania 4

Wątek - definicja. Wykorzystanie kilku rdzeni procesora jednocześnie Zrównoleglenie obliczeń Jednoczesna obsługa ekranu i procesu obliczeniowego

Języki i Techniki Programowania II. Wykład 7. Współbieżność 1

Podstawy współbieżności

Programowanie równoległe i rozproszone. Monitory i zmienne warunku. Krzysztof Banaś Programowanie równoległe i rozproszone 1

Stworzenie klasy nie jest równoznaczne z wykorzystaniem wielowątkowości. Uzyskuje się ją dopiero poprzez inicjalizację wątku.

Programowanie wielowątkowe. Tomasz Borzyszkowski

Programowanie równoległe i rozproszone. W1. Wielowątkowość. Krzysztof Banaś Programowanie równoległe i rozproszone 1

Współbieżność w Javie

Współbieżność w Javie

Programowanie współbieżne Wykład 2. Rafał Skinderowicz

Wielowątkowość mgr Tomasz Xięski, Instytut Informatyki, Uniwersytet Śląski Katowice, 2011

JAVA W SUPER EXPRESOWEJ PIGUŁCE

Dokumentacja do API Javy.

Przetwarzanie równoległe i współbieżne

Programowanie w Sieci Internet. Python: Wątki. Kraków, 12 grudnia 2014 r. mgr Piotr Rytko Wydział Matematyki i Informatyki

Wielowątkowość 1. Marcin Orchel. AGH University of Science and Technology in Poland 1 / 58

Wątki (Threads) Potrzeby. Przetwarzanie równoległe i współbieŝne. Cechy programowania wątkowego. Concurrent programming is like

Wyjątki. Streszczenie Celem wykładu jest omówienie tematyki wyjątków w Javie. Czas wykładu 45 minut.

Systemy Rozproszone - Ćwiczenie 4

Programowanie wielowątkowe. Jarosław Kuchta

Programowanie wielowątkowe: podstawowe koncepcje, narzędzia w Javie. J. Starzyński, JiMP2, rok akad. 2005/2006

1 Atrybuty i metody klasowe

Wykład 8: Obsługa Wyjątków

Programowanie współbieżne Wykład 5. Rafał Skinderowicz

WSNHiD, Programowanie 2 Lab. 2 Język Java struktura programu, dziedziczenie, abstrakcja, polimorfizm, interfejsy

Semafor nie jest mechanizmem strukturalnym. Aplikacje pisane z użyciem semaforów są podatne na błędy. Np. brak operacji sem_post blokuje aplikację.

Wykład 7: Pakiety i Interfejsy

1. Co można powiedzieć o poniższym kodzie (zakładając, że zaimportowano wszystkie niezbędne klasy)?

WIELOWĄTKOWOŚĆ. Waldemar Korłub. Platformy Technologiczne KASK ETI Politechnika Gdańska

Java: kilka brakujących szczegółów i uniwersalna nadklasa Object

Dawid Gierszewski Adam Hanasko

Obszar statyczny dane dostępne w dowolnym momencie podczas pracy programu (wprowadzone słowem kluczowym static),

Wykład 5. Synchronizacja (część II) Wojciech Kwedlo, Wykład z Systemów Operacyjnych -1- Wydział Informatyki PB

Metody Metody, parametry, zwracanie wartości

Języki i metody programowania Java. Wykład 2 (część 2)

Marcin Luckner Politechnika Warszawska Wydział Matematyki i Nauk Informacyjnych

Semafor nie jest mechanizmem strukturalnym. Aplikacje pisane z użyciem semaforów są podatne na błędy. Np. brak operacji sem_post blokuje aplikację.

Interfejsy w Java. Przetwarzanie równoległe. Wątki.

Aplikacja wielowątkowa prosty komunikator

Programowanie w języku Java - Wyjątki, obsługa wyjątków, generowanie wyjątków

Programowanie obiektowe

Informatyka I. Klasy i obiekty. Podstawy programowania obiektowego. dr inż. Andrzej Czerepicki. Politechnika Warszawska Wydział Transportu 2018

Programowanie współbieżne Laboratorium nr 12

Aplikacje Internetowe. Najprostsza aplikacja. Komponenty Javy. Podstawy języka Java

4.1 Napisz kod, w którym definiujesz, tworzysz oraz uruchamiasz wątki z uŝyciem klas java.lang.thread oraz java.lang.runnable.

Polimorfizm. dr Jarosław Skaruz

6.1 Pojęcie wątku programu 6.2 Klasy Timer, TimerTask 6.3 Klasa Thread 6.4 Synchronizacja pracy wątków 6.5 Grupowanie wątków

Kurs programowania. Wykład 1. Wojciech Macyna. 3 marca 2016

Aplikacje w środowisku Java

Kurs programowania. Wykład 3. Wojciech Macyna. 22 marca 2019

TEMAT : KLASY DZIEDZICZENIE

Model pamięci. Rafał Skinderowicz

PHP 5 język obiektowy

Wykład 6: Dziedziczenie

Programowanie współbieżne i rozproszone

Programowanie obiektowe

Interfejsy. Programowanie obiektowe. Paweł Rogaliński Instytut Informatyki, Automatyki i Robotyki Politechniki Wrocławskiej

Java: interfejsy i klasy wewnętrzne

Obiekt klasy jest definiowany poprzez jej składniki. Składnikami są różne zmienne oraz funkcje. Składniki opisują rzeczywisty stan obiektu.

Język JAVA podstawy. Wykład 3, część 3. Jacek Rumiński. Politechnika Gdańska, Inżynieria Biomedyczna

Programowanie obiektowe

Enkapsulacja, dziedziczenie, polimorfizm

Projektowanie aplikacji internetowych laboratorium

Programowanie obiektowe

Konstruktory. Streszczenie Celem wykładu jest zaprezentowanie konstruktorów w Javie, syntaktyki oraz zalet ich stosowania. Czas wykładu 45 minut.

Programowanie współbieżne Laboratorium nr 11

Zaawansowany kurs języka Python

Podstawy i języki programowania

Programowanie obiektowe

W Javie wątki są obiektami zdefiniowanymi za pomocą specjalnego rodzaju klas.

Klasy abstrakcyjne, interfejsy i polimorfizm

Aplikacje w środowisku Java

IMIĘ i NAZWISKO: Pytania i (przykładowe) Odpowiedzi

Aplikacje w środowisku Java

Systemy operacyjne. Zajęcia 11. Monitory

Multimedia JAVA. Historia

Programowanie obiektowe

dziedziczenie - po nazwie klasy wystąpią słowa: extends nazwa_superklasy

Zaawansowane programowanie w C++ (PCP)

JAVA. Java jest wszechstronnym językiem programowania, zorientowanym. apletów oraz samodzielnych aplikacji.

Klasy abstrakcyjne i interfejsy

Kurs programowania. Wykład 9. Wojciech Macyna. 28 kwiecień 2016

Transkrypt:

Java SE, Laboratorium nr 8 Wątki Wątki w Jawie umożliwiają współbieżność operacji. Wątki umożliwiają równoległe wykonywanie instrukcji. Wątki wymagają mniej zasobów niż osobne procesy. Wynika to z tego, że wątki są wykonywane w ramach procesu - każdy proces ma przynajmniej jeden wątek. Wątki współdzielą zasoby należne do danego procesu i to jest przyczyną z jednej strony ich wydajności, a z drugiej problemów komunikacji. Każdy program w Java ma przynajmniej jeden wątek - main thread, który ma zdolność do uruchamiania dalszych wątków. Każdy wątek w Java jest powiązany z instancją klasy Thread. Są dwie strategie tworzenia aplikacji wielowątkowych: bezpośrednia kontrola i zarządzanie wątkami (powoływanie do życia obiektów wątków), "abstrakcyjna" kontrola (zdajemy się na zewnętrznego wywoływacza). Tworzenie i uruchamianie wątków Są dwa sposoby tworzenia wątków: Poprzez powołanie do życia obiektu implementującego interfejs Runnable. (Listing1) Obiekt Runnable jest przekazywany do konstruktora Thread. Interfejs Runnable posiada jedną metodę run, w której umieszcza się instrukcje, które chcemy wykonać w ramach danego wątku. Poprzez powołanie do życia klasy dziedziczącej po klasie Thread. (Listing 2) Klasa Thread implementuje interfejs Runnable, a w nim ciało metody run jest puste. W obu przypadkach musimy wykonać Thread.start w celu odpalenia nowego wątku. //Listing 1 public class HelloRunnable implements Runnable { System.out.println("Hello from a thread!"); public static void main(string args[]) { (new Thread(new HelloRunnable())).start(); (new Thread(new HelloRunnable())).start(); 1

//Listing 2 public class HelloThread extends Thread { System.out.println("Hello from a thread!"); public static void main(string args[]) { (new HelloThread()).start(); Której z powyższych metod należy używać? Pierwsza metoda, implementacja interfejsu Runnable jest bardziej ogólna. Wynika to z tego, że nasza klasa może wtedy dziedziczyć od innej klasy - nie mamy zablokowanej hierarchii dziedziczenia. Drugi sposób jest prostszy do obsługi w typowych aplikacjach. Usypianie wątków Thread.sleep jest metodą, której wykonanie skutkuje zawieszeniem wykonywania instrukcji wątku, na rzecz którego metodę wykonano. Sleep jest skutecznym sposobem zarządzania czasem procesora, który na czas uśpienia jest przez dany wątek zwalniany na rzecz pozostałych wątków danej aplikacji oraz innych aplikacji w danym systemie. Metoda sleep jest używana do nadawania określonej kolejności, rytmu instrukcjom oraz do oczekiwania na różne wydarzenia. Są 2 przeciążone deklaracje sleep: przejmująca jako argument czas w milisekundach, przyjmująca jako argument czas w nanosekundach. Nie ma gwarancji, że faktyczny czas uśpienia danego wątku będzie dokładnie taki, jak przekażemy do sleep. Tłumaczy się to uwarunkowaniami różnych OS. Dodatkowo czas uśpienia wątku może zostać przerwany (o tym później). Podsumowując, nie możemy liczyć na precyzyjne usypianie wątków. //Listing 3 public class SleepMessages { public static void main(string args[]) throws InterruptedException { String importantinfo[] = { "Ala ma kota.", "A kot ma Ale.", "Ala lubi swojego kota.", "A kot lubi Ale." ; 2

for (int i = 0;i < importantinfo.length;i++) { //Pause for 4 seconds Thread.sleep(4000); //Print a message System.out.println(importantInfo[i]); Przerywanie wątkom Przerwa, ang. interrupt, wskazuje, że wątek ma zatrzymać bieżące operacje i ewentualnie wykonać coś innego. Programista określa jak wątek reaguje na przerwanie, na ogół wątek, któremu przerwano kończy się. Żeby poprawnie obsłużyć przerwanie wątek musi go umieć obsłużyć. W jaki sposób wątek jest w stanie obsłużyć swoje własne przerwanie? Sytuacja nr 1: w metodzie sprawdzamy wyjątek InterruptedException, narzucają nam to metody, które wykonujemy. Sytuacja nr 2: metody nie wymagają obsłużenia wyjątku w.w., wtedy musimy jawnie sprawdzić stan wątku, np. for (int i = 0; i < inputs.length; i++) { heavycrunch(inputs[i]); if (Thread.interrupted()) { return; Sytuacja nr 3: Sami wyrzucamy wyjatek, który później można obsłużyć w catch: public static void main(string[] args) { if (Thread.interrupted()) { throw new InterruptedException(); Mechanizm przerwań oparty jest na mechanizmie wewnętrznej flagi, tzw. interrupt status. Wykonanie Thread.interrupt powoduje ustawienie tej flagi. Stan tej flagi możemy sprawdzić wykonując metodę isinterrupted. Aby ustawić flagę wywołujemy Thread.interrupted. Co więcej, domyślnie każda metoda, która kończy zwróceniem wyjątku InterruptedException czyści flagę interrupt status. Należy jednak pamiętać, że status ten może bardzo szybko znów ulec zmianie. Oczekiwanie na zakończenie działania innego wątku Realizuje się metodą join. Załóżmy, że czekamy na wątek o nazwie t, wtedy w bieżącym wątku wywołujemy t.join(); W efekcie bieżący wątek jest w stanie pauzy, oczekuje na zakończenie się wątku t. 3

Metoda join występuje w wersjach przeładowanych, w których programista może nakazać wątkowi oczekiwanie na zakończenie się innego wątku przez zadany czas. Podobnie jak w przypadku sleep, nie ma jednak gwarancji że podany przez nas czas będzie faktycznie zgodny z faktycznym czasem oczekiwania. Metoda join w odpowiedzi na przerwanie zwraca wyjątek InterruptedException. //Listing 4 public class SimpleThreads { // Display a message, preceded by the name of the current thread static void threadmessage(string message) { String threadname = Thread.currentThread().getName(); System.out.format("%s: %s%n", threadname, message); private static class MessageLoop implements Runnable { String importantinfo[] = {"Ala ma kota.", "Kot ma Ale.", "Ala lubi kota.", "A kot lubi Ale." ; for (int i = 0; i < importantinfo.length; i++) { // Pause for 4 seconds Thread.sleep(4000); // Print a message threadmessage(importantinfo[i]); catch (InterruptedException e) { threadmessage("i wasn't done!"); private static class NumbersLoop implements Runnable { Integer mynumbers[] = {1,5,1,86,4,6,4,1,6,43,5,1,52,6,4,3,45,61,56,56,2,56,6,5,34,2; for (int i = 0; i < mynumbers.length; i++) { // Pause for 1 seconds Thread.sleep(1000); // Print a message threadmessage(mynumbers[i].tostring()); catch (InterruptedException e) { threadmessage("i wasn't done!"); public static void main(string args[]) throws InterruptedException { threadmessage("starting MessageLoop thread"); Thread t1 = new Thread(new MessageLoop()); 4

t1.start(); threadmessage("starting Numbers Loop thread"); Thread t2 = new Thread(new NumbersLoop()); t2.start(); while (t1.isalive() t2.isalive()) { threadmessage("still waiting..."); // Wait maximum of 1 second for threads to finish. t1.join(1000); t2.join(1000); threadmessage("finally!"); Synchronizacja Współdzielenie zasobów przez wątki prowadzi do konfliktów, których po części można uniknąć stosując mechanizm synchronizacji. Pamiętać należy jednak o uważnym synchronizowaniu zasobów w celu uniknięcia zagłodzenia/zablokowania wątku. Prosty przykład (tzw. thread inference): class Counter { private int c = 0; public void increment() { c++; public void decrement() { c--; public int value() { return c; Wątek A: pobierz wartość c Wątek B: pobierz wartość c Wątek A: zwiększ c o 1; wynik to 1 Wątek B: zmniejsz c o 1; wynik to -1 Wątek A: zapisz wynik; c to 1 Wątek B: zapisz wynik; c to -1 5

W celu uniknięcia sytuacji jak powyżej mamy do dyspozycji: mechanizm synchronizacji metod, mechanizm synchronizacji instrukcji. Synchronizacja metod: public class SynchronizedCounter { private int c = 0; public synchronized void increment() { c++; public synchronized void decrement() { c--; public synchronized int value() { return c; W efekcie, nie jest możliwe, żeby dwie synchronizowane metody wykonywały się na rzecz obiektu z danej klasy równolegle. Wykonywanie się jednej z synchronizowanych metod powoduje, że pozostałe metody są zablokowane (wstrzymane) dopóki dany wątek nie zakończy działającej metody. Po zakończeniu działania metody synchronizowanej zmiany dokonane na rzecz danego obiektu są widoczne w pozostałych wątkach. Synchronizacja polega na zbudowaniu wewnętrznej struktury blokady monitora (intrinsic lock/monitor lock). Monitor umożliwia wyłączność dostępu do danego zasobu. Każdy obiekt posiada swój własny monitor. Wątek wymagający dostępu do danego obiektu musi "przejąć" monitor tego obiektu przed jego wykorzystaniem oraz zwolnić po. Mówi się o posiadaniu monitora przez dany wątek - jest to czas od przejęcia do zwolnienia monitora. Dwa wątki równolegle nie mogą posiadać tego samego monitora, wątek już posiadający monitor blokuje do niego dostęp. Wywołanie metody synchronizowanej powoduje automatyczne przejęcie obiektów z tej metody przez wątek. Zwolnienie monitora następuje: po zakończeniu metody zarówno w sposób naturalny (return) jak i poprzez nastąpienie wyjątku. Jeśli metoda jest typu static, metoda synchronizowana przejmuje monitor obiektu związanego z daną klasą: Class object. Stąd, monitor na obiekcie klasy powoduje, że nie jest możliwy np. dostęp do pól statycznych z poziomu żadnej instancji tej klasy. Synchronizacja (bloku) instrukcji: Drugim sposobem synchronizacji w Java jest synchronizacja instrukcji. Synchronizując blok konieczne jest jawne wskazanie obiektu, którego monitor chcemy przejąć, np.: 6

public void addname(string name) { synchronized(this) { lastname = name; namecount++; namelist.add(name); W powyższym przykładzie widzimy, że synchronizacją objęte są pola lastname i namecount, natomiast metody innych obiektów tu obiekt namelist nie powinny być synchronizowane. Synchronizując i przejmując monitor obiektu B z poziomu obiektu A możemy niesłusznie spowodować problem z dostępem do obiektu B z poziomu jego własnych metod. Gdyby nie można było synchronizować instrukcji, metoda powyżej musiała by zostać podzielona na 2 metody. Metoda 2 wyłącznie dodawałaby name do namelist. Synchronizacja instrukcji jest stosowana również do poprawy współbieżności programu na poziomie pojedynczych instrukcji, np.: public class MsLunch { private long c1 = 0; private long c2 = 0; private Object lock1 = new Object(); private Object lock2 = new Object(); public void inc1() { synchronized(lock1) { c1++; public void inc2() { synchronized(lock2) { c2++; Pamiętać należy o ostrożnym stosowaniu konstrukcji jak powyżej. Powtórna synchronizacja: wątek może ponownie przejąć monitor zajęty przez siebie samego. Dzięki temu łatwiejszej jest pisanie kodu, bowiem z poziomu synchronizowanej metody A możemy jawnie/niejawnie wywołać synchronizowaną metodę B ponownie przejmującą już zajęty przez siebie samego monitor. Gdyby nie było takiego mechanizmu bardzo łatwo by było napisać samoblokujący się kod. Dostęp atomiczny (atomic): Przez pojęcie atomiczny rozumiemy taki proces/akcję, która wykonuje się albo w całości albo wcale. Przed zakończeniem wykonywania atomicznej czynności na zewnątrz nie widać żadnych jej efektów. Wybrane czynności są/mogą być atomiczne: 7

czytanie/zapis zmiennych prymitywnych za wyjątkiem typów long i double, odczyt/zapis wszystkich zmiennych zadeklarowanych jako volatile, w tym zmiennych typu long i double. Pamiętać należy, że używanie wyłącznie słowa kluczowego volatile nie rozwiązuje wszystkich problemów współbieżnego dostępu wątków do zasobów. Niepożądane stany wątków Zakleszczenie następuje wtedy, gdy dwa lub więcej wątki zostają zablokowane na stałe w oczekiwaniu na siebie. Zagłodzenie następuje wtedy, kiedy wątek nie ma regularnego dostępu do współdzielonych zasobów i nie może ukończyć działania. Typowo zagłodzenie powstaje kiedy napiszemy algorytm szeregowania z niesprawiedliwym podziałem zasobów. Livelock następuje wtedy, kiedy dwa (lub więcej) wątków nie jest w stanie kontynuować normalne działanie wysyłając sobie równocześnie nawzajem sygnał do rozpoczęcia pracy. Bloki strzeżone; wait oraz notifyall W celu tymczasowego wstrzymania działania wątku, np. w sytuacji gdy nastąpiło jakieś zdarzenie, zaleca się pisanie bloków strzeżonych z instrukcją wait, np. public synchronized void guardedpart() { while(!cond) { wait(); catch (InterruptedException e) { System.out.println("Desired condition cond has been satisfied!"); Zaleca się stosowanie wait przy oczekiwaniu wątku na spełnienie warunku. Stosowanie wait niesie za sobą wyjątek InterruptedException. W kodzie powyżej jego ciało jest puste, ponieważ interesuje nas wyłącznie warunek cond. Uzupełnieniem metody powyżej jest metoda, w której nastąpić może zmiana zmiennej cond: public synchronized notifypart() { cond = true; notifyall(); 8

Widzimy tu metodę notifyall(), która informuje wszystkie wątki czekające na odblokowanie monitora, że nastąpiła zmiana. Dostępna jest również metoda notify(), która informuje i budzi jeden obiekt. Nie mamy jednak kontroli nad tym, który z wątków zostanie obudzony. Z tej przyczyny notify() wykorzystywany jest np. w aplikacjach przetwarzających dane, gdzie wątki współbieżnie wykonują wiele zbliżonych jakościowo zadań. //Listing 5. Zrodlo: Tutorials Oracle public class Drop { // Message sent from producer // to consumer. private String message; // True if consumer should wait // for producer to send message, // false if producer should wait for // consumer to retrieve message. private boolean empty = true; public synchronized String take() { // Wait until message is // available. while (empty) { wait(); catch (InterruptedException e) { // Toggle status. empty = true; // Notify producer that // status has changed. notifyall(); return message; public synchronized void put(string message) { // Wait until message has // been retrieved. while (!empty) { wait(); catch (InterruptedException e) { // Toggle status. empty = false; // Store message. this.message = message; // Notify consumer that status // has changed. notifyall(); 9

import java.util.random; public class Producer implements Runnable { private Drop drop; public Producer(Drop drop) { this.drop = drop; String importantinfo[] = { "Mares eat oats", "Does eat oats", "Little lambs eat ivy", "A kid will eat ivy too" ; Random random = new Random(); for (int i = 0; i < importantinfo.length; i++) { drop.put(importantinfo[i]); System.out.println("wsadzono: " + importantinfo[i]); Thread.sleep(random.nextInt(5000)); catch (InterruptedException e) { drop.put("done"); import java.util.random; public class Consumer implements Runnable { private Drop drop; public Consumer(Drop drop) { this.drop = drop; Random random = new Random(); for (String message = drop.take();!message.equals("done"); message = drop.take()) { System.out.format("MESSAGE RECEIVED: %s%n", message); Thread.sleep(random.nextInt(5000)); catch (InterruptedException e) { public class ProducerConsumerExample { public static void main(string[] args) { Drop drop = new Drop(); (new Thread(new Producer(drop))).start(); (new Thread(new Consumer(drop))).start(); 10

Wątki daemony Wątek daemon to taki wątek, który automatycznie kończy działanie wraz z zakończeniem wykonywania się wątków nie-daemonów. Typowo daemony to usługi, które działają na rzecz zwykłych wątków. W celu ustawienia wątku jako daemona wystarczy: setdaemon(true); Zadania 1. Przygotuj klasę wątku, który na konsolę wypisze k razy komunikat msg; k i msg to argumenty konstruktora. W f. main uruchom 3 takie wątki dla: a. 20, Ala ma kota. b. 10, xyzxyzxyzxyzxyzxyzxyzxyz c. 30,.. 2. Przygotuj klasę wątku, który na konsolę wypisze k razy komunikat msg, po czym zostanie uśpiony na p sekund, p, k oraz msg są argumentami konstruktora. W f. main uruchom 3 takie wątki dla: a. 2, 20, Ala ma kota. b. 3, 10, xyzxyzxyzxyzxyzxyzxyzxyz c. 1, 30,.. Uśpienie wątku umieść w pętli wypisującej komunikat. 3. Przygotuj klasę wątku-daemona, która w pętli nieskończonej na konsoli wypisywać będzie słowo jestem daemonem!!!! W f. main uruchom jeden taki wątek. Aby lepiej zobaczyć efekt działania w f. main uruchom pętlę wypisującą w konsoli funkcja main 10 razy. 4. Przygotuj klasę wątku modtablicy, która w tablicy intów zamieni najmniejszy jej element na liczbę mojaliczba, mojaliczba to argument konstruktora wątku. Po dokonaniu zamiany wątek śpi przez mojsen sekund, mojsen to drugi argument konstruktora wątku. Jeśli nie ma liczby mniejszej od mojaliczba wątek kończy działanie. Zainicjalizuj tablicę 20 intów zerami. Jest to ta tablica, którą modyfikować będą wątki. Uruchom 4 wątki dla argumentów (20, 3), (11,1), (30,1) i (15,4). Dodaj metodę wypisującą w nowej linii zawartość tablicy po każdej modyfikacji. a) przygotuj wersję, gdzie tablica intów będzie polem składowym klasy b) przygotuj wersję, gdzie tablica intów będzie synchronizowaną kolekcją, np. java.util.concurrent.blockingqueue; 11