Wątki, programowanie współbieżne Krystian Ignasiak kmi@ire.pw.edu.pl http://tiger.ire.pw.edu.pl/pzdt
Zalety programowania współbieżnego odpowiadanie na zdarzenia dostępność usług łatwość sterowania jeden obiekt – jeden wątek przetwarzanie równoległe
Wady programowania współbieżnego problem bezpieczeństwa programu (safety) możliwość wystąpienia zakleszczenia i innych sytuacji wpływających na żywotość programów (liveness) zachowania niedeterministyczne wymagania dodatkowej mocy obliczeniowej do tworzenia wątków i przełączania między wątkami w środowiskach jednoprocesorowych; wymagania dodatkowej mocy obliczeniowej na synchronizację wątków;
Co to jest wątek (thread) ? Zestaw trzech elementów: Procesor Pamięć programu (program, kod) Pamięć danych (dane)
Wątki w Javie public class ThreadTester { public static void main(String args[]) { HelloRunner r = new HelloRunner(); Thread t = new Thread(r); t.start(); } } class HelloRunner implements Runnable { int i; public void run() { i = 0; while (true) { System.out.println(”Hello ” + i++); if (i == 50) { break; } } } }
Tworzenie wątków
Uruchamianie wątku th.start() Wywołanie metody start powoduje przejscie wątku do stanu uruchomiony Wywołanie start nie musi oznaczać, że sterowanie natychmiast znajdzie się w metodzie run tego wątku
Kolejkowanie wątków Kolejkowanie zwykle bazuje na priorytetach (jednak nie ma gwarancji, ze konkretna implementacja w ogóle używa priorytetów) th.getPriority(), th.setPriority() Priorytet – liczba w zakresie 1-10 Thread.MIN_PRIORITY, Thread.MAX_PRIORITY, Thread.NORM_PRIORITY Java zwykle kolejkuje wątki metodą z wywłaszczaniem procesora (preemptive) Niektóre implementacje stosują metodę z podziałem czasu (timeslicing)
Zakończenie działania wątku public class Runner implements Runnable { private boolean timeToQuit=false; public void run() { while ( ! timeToQuit ) { ... } // ustaw obiekt wątku w odpowiednim stanie } public void stopRunning() { timeToQuit=true; } } public class ThreadController { private Runner r = new Runner(); private Thread t = new Thread(r); public void startThread() { t.start(); } public void stopThread() { r.stopRunning(); } }
Sterowanie wątkami isAlive() Thread.sleep() join() Thread.yield()
Dwa sposoby tworzenia wątku Dziedziczenie po klasie Thread Mniej skomplikowany kod Implementowanie Runnable Łatwiejsze projektowanie obiektowe Omijamy ograniczenie pojedynczego dziedziczenia Spójność z modelem (procesor, kod, dane)
Stany wątków, 1/2
Stany wątków, 2/2
Synchronizacja, 1/2 Dwa wątki (lub więcej) korzystają w tym samym czasie z danych jednego obiektu – może powstać konflikt (read-write, write-write) Gwarantuje się, że prymitywne operacje (dostęp do zmiennej typu prostego, oprócz typów long i double) będą wykonane w całości bez konieczności jawnego synchronizowania) Kompilator może dokonywać optymalizacji dostępu do danych (volatile);
Synchronizacja, 2/2 Bloki synchronizowane synchronized (myObject) { ... } Bloki synchronizowane są wykonywane w całości (chyba, że wait()...) Metoda synchronizowana może wywołać inną synchronizowaną na rzecz tego samego obiektu bez blokowania Słowo kluczowe synchronized może być przedefiniowane w klasach pochodnych Metody deklarowane w interfejsach nie mogą być synchronizowane
wait(), notify() Metody wait(), notify() mogą być wywołane tylko w ramach bloku synchronizowanego wait(): zatrzymanie aktualnego wątku interpreter umieszcza wątek w kolejce związanej z obiektem blokada synchronizacyjna jest zwalniana dla tego obiektu notify(): Zwalnia się blokadę synchronizacyjną dla obiektu (O) Jeśli istnieje wątek T (związany z obiektem O), żądający wykonania bloku synchronizowanego – uzyskuje monitor O i jest usuwany z kolejki wątków związanej z O T jest wznawiany za wywołaniem wait(), które spowodowało zatrzymanie