Język Java Wielowątkowość
Wątki Podział programu na kawałki, z których każdy uruchamia się na innym procesorze, np. serwery WWW – oprogramowanie dzielące zadania – obsługę poszczególnych użytkowników Wątek (ang. thread) – wykonuje zadanie działając niezależnie od głównego programu, pojedynczy sekwencyjny przepływ sterowania w obrębie procesu Z punktu widzenia użytkownika - jednoczesne wykonywanie wielu czynności
Programowanie współbieżne Obsługa każdego wątku przez odrębny procesor (tzw. programowanie równoległe) Więcej wątków niż procesorów - emulowanie współbieżności Cechy programowania współbieżnego polepszenie zarządzania zasobami większa wygoda użytkownika emulacja - zmniejszenie wydajności
Szybsze wykonanie zadań Na jednym procesorze konieczne jest przełączanie kontekstu powodujące narzut – opłaca się tylko wtedy gdy wątek jest blokowany np. poprzez operacje I/O Od JDK 1.4 nieco porawione przełączanie między wątkami tak, aby wszystkie były równouprawnione Niektóre programy są wielowątkowe z założenia – servlet
Szybsze wykonanie zadań Przykładem wykorzystania wielowątkowości jest programowanie sterowane zdarzeniami – program wykonuje obliczenia i reaguje na działania użytkownika W przypadku systemu operacyjnego współbieżność realizowana jest na poziomie procesu (programu z przydzieloną pamięcią) Java nie rozwidla procesów, ale tworzy wiele wątków w jednym procesie – uniezależnienie od systemu operacyjnego
Wielowątkowość w Javie Język tworzy osobny stos i osobne rejestry dla każdego wątku koordynuje i szereguje wątki odśmiecacz (garbage collector) nie usuwa wątków nawet gdy nie ma do nich referencji Szeregowanie wątków - określanie dostępu do procesora pierwszeństwo - wątki o wyższym priorytecie wątki równoprawne - przełączane na zmianę
Programowanie wątków Klasa java.lang.Thread klasa bazowa wątków zawiera metody konieczne do obsługi wątków Główne metody wątku (domyślnie puste) run() - określa zachowanie wątku (zadanie do wykonania) start() - uruchamia wątek, wywołuje run() Metody programowania wątków utworzenie klasy pochodnej po Thread implementacja interfejsu Runnable w celu utworzenia zadania przekazywanego następnie do wątku (obiektu klasy Thread) lub wykonawcy (java.util.concurrent.Executor (SE5))
Cykl życia wątku run() start() nowy wątek wątek uruchamialny blokowanie nowy wątek wątek uruchamialny wątek zablokowany zakończenie metody run() wątek uśmiercony
Dziedziczenie po klasie Thread - aplikacja TestWatkow class MojWatek extends Thread { int numer; public MojWatek(int numer) { super(); this.numer = numer; } public void run() { System.out.println("Watek: " + numer);
public class TestWatkow { public static void main(String args[]) { MojWatek w1, w2, w3; w1 = new MojWatek(1); w2 = new MojWatek(2); w3 = new MojWatek(3); w1.start(); w2.start(); w3.start(); System.out.println("Watek: 0"); }
Użycie interfejsu Runnable Zastosowanie - w klasach mających klasę bazową Implementacja interfejsu w klasie głównej - zawiera tę samą metodę run() Tworzenie wątku - obiektu klasy Thread Argument konstruktora - obiekt klasy, w której określono metodę run() wątku Często wewnątrz metody run występuje pętla nieskończona wykonująca zadania dopóki wątek jest nam potrzebny, wtedy konieczne jest zastosowanie warunku powrotu z return, dodatkowo w pętli może być dodana instrukcja Thread.yeld() sugestia dla thread schedulera (planisty) żeby przełączył na chwilę na inny wątek
Użycie wykonawcy (od JDK 1.5) Obiekt Executor pośredniczy pomiędzy klientem a wykonaniem zadania – nie wywołujemy zadania bezpośrednio Eliminuje konieczność jawnego zarządzania cyklem życia wątku i synchronizacji wątków przy dostępie do zasobów współdzielonych Klasa ExecutorService konstruuje kontekst uruchomienia zadania (Runnable) – obiekt tej klasy tworzony jest poprzez klasę Executors: Executors.newCachedThreadPool()- dodaje do puli kolejne wątki wykonujące kolejne zadania, gdy zwolni się jakiś wątek kolejne zadanie przypisywane jest do niego
Użycie wykonawcy II Executors.newFixedThreadPool(int)- rozmiar puli jest od razu ustalony – strata z narzutu na tworzenie wątków ponoszona tylko raz na początku podczas tworzenia puli – najlepsze rozwiązanie w kodzie produkcyjnym – watki obsługiwane są szybciej, uniemożliwia przekroczenie dopuszczalnej przez podsystem ilości wątków – lepsze zarządzanie obsługą zdarzeń Executors.newSingleThreadExecutor()- pula wątków uruchamiająca jeden wątek jednocześnie – umożliwia zsynchronizowany dostęp do wspólnych zasobów np. drukarki, tworzy niejawną kolejkę w której umieszczane są zadania i wykonywane kolejno; mamy pewność że nie uszkodzimy systemu plików Executors.newSingleThreadScheduledExecutor() – umożliwia uruchamianie zadań z opóźnieniem lub cyklicznie Executors.newScheduledThreadPool(int) Metoda executor.shutdown() – blokada wykonania kolejnych wątków przez wykonawcę
Użycie wykonawcy - przykład import java.util.concurrent.*; public class NoweWatki{ public static void main(String args[]){ ExecutorService exe = Executors.newFixedThreadPool(2); for(int i=0; i<2; i++){ exe.execute(new ObiektRunnable()); } exec.shutdown();
Zadania zwracające wartość (JDK 1.5) Implementacja interfejsu Runnable nie zwraca wartości – metoda public void run() W celu zwrócenia wartości – zastosowanie interfejsu Callable oraz metody call() zastępującej run(), zadanie uruchamiane za pomocą metody submit()rozszerzającą możliwości metody execute interfejsu ExecutorService Wynik submit() – obiekt Future który można wykorzystać do przerwania zadania albo odczekania do zakończenia jego wykonywania cancel(boolean) – true –przerwanie wątku zadania, false – odczekanie do zakończenia jeśli już uruchomione isCancelled(), isDone(), get() – czeka i zwraca wynik
Zadania zwracające wartość import java.util.concurrent.*; class Zadanie implements Callable<String>{ public String call(){ return „Wynik wywołania zadania w wątku” } public class TestZadania{ public static void main(String[] args){ ExecutorService exe = Executors.newCachedThreadPool(); Future<String> fut = exe.submit(new Zadanie()); System.out.println(fut.get()); //fut.get(long czas, //TimeUnit tu) } //tu = TimeUnit.MILISECONDS lub NANOSECONDS lub //MICROSECONDS lub SECONDS – zwraca wynik jeśli jest
Metody blokowania wątków Uśpienie na n milisekund - wywołanie metody sleep(n), np. try { //dawniej Thread.sleep(1000); TimeUnit.SECONDS.sleep(1); //JDK 1.5 }catch (InterruptedException e){ } //sleep nie zwalnia blokady obiektu Zawieszenie wykonania (tylko wewnątrz metody synchronizowanej) - wywołanie metody wait() Automatyczne - przy operacjach We/Wy
Wątki w apletach - animacje Etapy tworzenia animacji tworzenie rysunku („ramki” animacji) wyświetlenie ramki - złudzenie ruchu Użycie metod apletu start() - tworzenie nowego wątku apletu (możliwość wykonywania współbieżnie z innymi programami w systemie) stop() - zatrzymuje wątek
Aplet Zegar (cz. 1) import javax.swing.*; import javax.swing.border.*; import java.awt.*; import java.util.*; import java.text.DateFormat; public class Zegar extends JApplet implements Runnable { JLabel napis = new JLabel("", JLabel.CENTER); Thread watek;
Aplet Zegar (cz. 2) public void init() { Font f = new Font("serif", Font.BOLD, 24); Container kont = getContentPane(); napis.setOpaque(true); napis.setFont(f); napis.setBackground(Color.lightGray); napis.setForeground(Color.red); Border b = BorderFactory.createRaisedBevelBorder(); napis.setBorder(b); kont.add(napis, BorderLayout.CENTER); }
Aplet Zegar (cz. 3) public void start() { // metoda apletu if (watek == null) { watek = new Thread(this); watek.start(); } public void stop() { // metoda apletu if (watek != null) { watek = null;
Aplet Zegar (cz. 4) public void run() { // metoda wątku Thread watekAktualny = Thread.currentThread(); while (watek == watekAktualny) { Calendar cal = Calendar.getInstance(); Date data = cal.getTime(); DateFormat df = DateFormat.getTimeInstance(); napis.setText(df.format(data)); napis.repaint(); try { Thread.sleep(1000); } catch (InterruptedException e) { } }
Priorytety wątków Metody obsługi priorytetów (w praktyce rzadko używane) odczytanie priorytetu - getPriority() ustawianie priorytetu - setPriority() Argument metod - liczba całkowita z zakresu od Thread.MIN_PRIORITY do Thread.MAX_PRIORITY (JDK – 10 poziomów, Sun Solaris 2^31) Jeżeli chcemy ustawić dla bieżącego wątku możemy go uzyskać za pomocą Thread.currentThread()
Wątki demony Demon – wątek działający w tle programu nie związany bezpośrednio z jego głównymi zadaniami Program kończy swoje działanie kiedy zakończą się wszystkie wątki nie będące demonami Ustawienie demoniczności wątku metoda setDaemon(boolean) – można ją wywołać zanim wątek zostanie uruchomiony (metodą start()) Metoda isDeamon() – sprawdza demoniczność Każdy wątek wywołany z demona także jest demonem W przypadku gdy używamy Executora konieczność stworzenia własnej fabryki wątków – klasy implementującej interfejs ThreadFactory (zawierającej metodę Thread newThread(Runnable r)), której obiekt możemy użyć w metodzie execute(new MojaFabryka())
Synchronizacja wątków Konieczność - np. współdzielenie zasobów (sekcja krytyczna - izolowana część kodu) Sposoby synchronizacji blokowanie obiektu - użycie w różnych wątkach tzw. metod synchronizowanych blokowanie obiektu i wątku - użycie w metodach synchronizowanych metod wait() i notify()
Metody synchronizowane Określone modyfikatorem synchronized, np. public synchronized int czytaj() ( ... } public synchronized void pisz(int x) { ... } Wywołanie metody - dla pewnego obiektu Wynik - blokada danego obiektu dla innych metod synchronizowanych
Użycie metod wait() i notify() Blokowanie i obiektu, i wątku na nieokreślony czas - wait() – zwalnia blokadę obiektu na n milisekund - wait(n) Od JDK 1.5 klasa java.util.concurrent.locks.Condition i metoda await() Ponowne uruchamianie wątku notify() - zdejmuje blokadę z danego wątku notifyAll() - odblokowuje wszystkie wątki oczekujące na dany obiekt –lepiej użyć tej metody bo nie wiemy ile zadań oczekuje po upływie n milisekund Od JDK 1.5 klasa Condition i metoda signal() Wszystkie metody - klasy java.lang.Object nie Thread – bo operują blokadą, która jest częścią każdego obiektu Wywołanie w metodzie niesynchronizowanej – IllegalMonitorStateException, „Current thread not owner”
Przykład Producent-Klient generuje liczbę z przedziału 0÷9 zapisuje ją w obiekcie klasy Dane wyświetla wygenerowaną liczbę wykonuje czynności nieregularnie - zasypia" na przypadkowy czas z zakresu 0÷100 [ms] Klient wczytuje liczbę z obiektu klasy Dane wyświetla wczytaną liczbę
Klasa Producent class Producent extends Thread { private Dane dane; public Producent(Dane c) { dane = c; } public void run() { for (int i = 0; i < 10; i++) { dane.pisz(i); try { sleep((int)(Math.random()*100)); } catch (InterruptedException e) {} }
Klasa Klient class Klient extends Thread { private Dane dane; public Klient(Dane c) { dane = c; } public void run() { int x = 0; for (int i = 0; i < 10; i++) { x = dane.czytaj(); }
Producent-Klient: klasa Dane public class Dane { private int d; private boolean dostepne = false; public synchronized int czytaj() { while (dostepne == false) { try { wait(); } catch (InterruptedException e) {} } System.out.println("Odczyt: " + d); dostepne = false; notifyAll(); return d;
public synchronized void pisz(int x) { while (dostepne == true) { try { wait(); } catch (InterruptedException e) {} } System.out.println("Zapis: " + x); d = x; dostepne = true; notifyAll();
Producent-Klient: aplikacja TestPK public class TestPK { public static void main(String[] args){ Dane d = new Dane(); Producent p1 = new Producent(d); Klient k1 = new Klient(d); p1.start(); k1.start(); }
Własna blokada (od 1.5) import java.util.concurrent.locks.*; public class Blokada { private int wartosc; private Lock blokada = new ReentrantLock(); public void zwieksz(){ blokada.lock();//blokada.tryLock(czas, TimeUnit) try{ ++wartosc; }finally{ blokada.unlock(); } public static void main(String[] args){ …
Wątki w Swingu Rodzaje wątków w Swingu Wątki inicjujące - Initial threads – uruchamiające aplikacje, wywołujące metodę main, init (applet), w Swingu tworzą obiekt Runnable inicjalizujący GUI i przekazują do wykonania w wątku event dispatch Wątki wysyłające zdarzenia – Event dispatch thread – wykonują obsługę zdarzeń Wątki działające w tle – Worker hreads – javax.swing.SwingWorker, metoda done
Wątki w Swingu Zasada naczelna: unikać wątków, jeśli to możliwe bo Swing uruchamia własny wątek aktualizujący interfejs podczas działania uzytkownika i może dojść do zakleszczeń Wykonywanie kodu zależnego od stanu zrealizowanego komponentu (wyświtlonego) - tylko w wątku wysłanym przez zdarzenie (ang. event-dispatching thread) w tym wątku inne wątki np. wątek z main powinny umieszczać zadania Nie umieszczać długich wykonań w wątku rozprowadzającym zdarzenia (w metodzie obsługi zdarzenia) – nie może odrysować komponentów Komponent zrealizowany - narysowany lub gotowy do narysowania
Wątki w Swingu Uaktualnienie komponentu bez obsługi zdarzenia - metody klasy SwingUtilities z pakietu javax.swing - invokeLater() lub invokeAndWait() Uaktualnianie komponentów z opóźnieniem lub co pewien czas - klasa Timer
Wątki w Swingu public class Etykietka{ public static void main(String[] args){ JFrame f = new JFrame(); JLabel et = new JLabel(); f.add(et); f.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); TimeUnit.SECONDS.sleep(3); SwingUtilities.invokeLater(new Runnable(){ public void run(){ et.setText(„Witam po trzech sekundach!”): } });
Klasy Timer i TimerTask Dodane w wersji 1.3 (pakiet java.util) Programowanie wątków utworzenie klasy potomnej po TimerTask przesłonięcie metody run() utworzenie obiektu wątku klasy Timer utworzenie obiektu klasy potomnej po TimerTask (zadania) określenie sposobu wykonywania zadania
Klasy Timer i TimerTask Wykonywanie zadania - metoda schedule() opóźnienie o n milisekund schedule(TimerTask zadanie, long n) uruchomienie o określonej porze schedule(TimerTask zadanie, Date d) powtarzanie co x milisekund, np. schedule(TimerTask zadanie, long n, long x) Zatrzymywanie wątku - metoda cancel()
Problemy z wątkami Impas (ang. deadlock) - zamknięta pętla wątków, czekających na siebie nawzajem Programy wykorzystujące wątki skomplikowane utrudnione wykrywanie błędów