Wątki w Javie – programowanie współbieżne
Pojęcie wielowątkowości jest od dawna znane użytkownikom komputerów. Od wielu lat na rynku występują procesory wielordzeniowe, które pozwalają na pracę kilku procesów systemowych na raz. Jednakże w czasach, kiedy nie były one standardem również istniała współbieżność – była imitowana przez wywłaszczanie procesów i przyznawanie czasu pracy procesora innemu.
Procesy
Proces jest elementem systemu operacyjnego, który służy do organizacji wykonywania programu tak, aby dostępne były odpowiednie zasoby np. procesor, pamięć. Ważną cechą jest to, że każdy proces posiada swoje dane, identyfikator (PID). W związku z tym w przypadku aplikacji wieloprocesorowej wymagana jest komunikacja między nimi.
Wątki
Wątki w odróżnieniu od procesów są dużo lżejsze i są ich częścią. Proces składa się z co najmniej jednego wątku, ale może być ich więcej.
Główną różnicą jest to, że zasoby są współdzielone między wątkami w wewnątrz jednego procesu. Daje to rozmaite korzyści np. skrócenie czasu oczekiwania na wykonanie zadań aplikacji. Wielowątkowość nie jest jednak pozbawiona wad i jedną z nich jest problem spójności danych. Aby temu zapobiec należy synchronizować pracę wątków, co może przysporzyć sporo problemów.
Uruchomienie kilku wątków jest niezbędne w przypadku bardziej złożonych aplikacji. Wyobraźmy sobie aplikację typu przeglądarka internetowa. Zazwyczaj użytkownicy przeglądają różne strony internetowe, pobierają pliki, słuchają muzyki (z karty grającej w tle). Dzieje się tak dlatego, że przeglądarka jest aplikacją wielowątkową. Gdyby było inaczej nawet ściąganie plików zablokowałoby jakąkolwiek możliwość ingerencji w działanie programu – nawet zamknięcia aplikacji.
Tworzenie wątków
Technicznie tworzenie nowych wątków jest prostym zadaniem. Musimy wykonać jedynie kilka czynności.
Utworzenie klasy
Zaczynamy od utworzenia prostej klasy implementującej interfejs Runnable
.
1 2 3 4 5 |
public class ThreadTest implements Runnable { public void run() { //Polecenia wykonywane w osobnym wątku } } |
Uruchomienie wątków
Gdy mamy już klasę implementującą interfejs Runnable
wystarczy utworzyć obiekty, a następnie przy ich pomocy obiekty klasy Thread
. Następnie uruchomić wątek przy pomocy metody start()
.
1 2 3 |
Runnable r = new ThreadTest(); Thread t = new Thread(r); t.start(); |
Przykładowy program
Wyobraźmy sobie program, który posiada kilka funkcjonalności i każda do swojego wykonania zajmuje określoną ilość czasu procesora. W naszym przykładzie będą to czasy 1000, 500 i 100 ms. Ma to na celu pokazanie, że różne procedury mogą wymagać innej ilości czasu do wykonania. Równie dobrze pierwsza operacja mogłaby wymagać znacznie więcej czasu, a dwie pozostałe tyle samo. Dzięki zastosowaniu wątków procesor może obsłużyć te krótkie operacje wcześniej i nie trzeba czekać na zakończenie tej pierwszej – najdłuższej. Ma to znaczenie w przypadku graficznego interfejsu użytkownika, gdy nie może on wcisnąć przycisku zatrzymania operacji.
W klasie ThreadTest
będziemy przechowywać nr wątku oraz czas wykonywania operacji w nim zawartej. W rzeczywistości żaden wątek nic nie będzie robił. Oczekiwanie ma za zadanie symulację wykonywania jakiegoś algorytmu. Wykonamy je metodą sleep()
z klasy Thread
.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 |
public class ThreadTest implements Runnable { private int number; private int milis; ThreadTest (int n, int ms){ number = n; milis = ms; } @Override public void run() { while (true) { System.out.println("Obliczenia wątku nr: " + number); try { Thread.sleep(milis); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void main(String[] args) { Runnable r1 = new ThreadTest(1, 1000); Runnable r2 = new ThreadTest(2, 100); Runnable r3 = new ThreadTest(3, 500); Thread t1 = new Thread(r1); Thread t2 = new Thread(r2); Thread t3 = new Thread(r3); t1.start(); t2.start(); t3.start(); } } |
Jak widać nie trzeba bezpośrednio wywoływać metody run()
– jest ona uruchamiana po wywołaniu metody start()
. Przykładowym wynikiem uruchomienia programu może być następująca.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
Obliczenia wątku nr: 1 Obliczenia wątku nr: 3 Obliczenia wątku nr: 2 Obliczenia wątku nr: 2 Obliczenia wątku nr: 2 Obliczenia wątku nr: 2 Obliczenia wątku nr: 2 Obliczenia wątku nr: 3 Obliczenia wątku nr: 2 Obliczenia wątku nr: 2 Obliczenia wątku nr: 2 Obliczenia wątku nr: 2 Obliczenia wątku nr: 2 Obliczenia wątku nr: 1 Obliczenia wątku nr: 3 Obliczenia wątku nr: 2 ... |
Wątek nr 1, który trwa najdłużej nie blokuje programu i równolegle wykonują się pozostałe wątki. Następnie czeka znowu na czas procesora i wykonuje się ponownie.