Przedmowa. Wujek Petya
Powiedzmy, że chcemy napełnić butelkę wodą. Dostępna jest butelka i kran z wodą wujka Petyi. Wujek Petya zamontował dziś nowy kran i ciągle wychwalał jego piękno. Wcześniej używał tylko starego, zatkanego kranu, więc kolejki przy butelkowaniu były ogromne. Po chwili grzebania słychać było od strony rozlewania się dźwięk nalewania wody, po 2 minutach butelka jest jeszcze w fazie napełniania, za nami utworzyła się zwykła kolejka, a w mojej głowie pojawia się obraz jak troskliwy Wujek Petya wybiera do naszej butelki tylko najlepsze cząsteczki H2O. Wujek Petya, wytrenowany przez życie, uspokaja szczególnie agresywnych i obiecuje skończyć jak najszybciej. Skończywszy butelkę, bierze następną i odkręca zwykłe ciśnienie, co nie ujawnia wszystkich możliwości nowego kranu. Ludzie nie są szczęśliwi...
Teoria
Wielowątkowość to zdolność platformy do tworzenia wielu wątków w ramach jednego procesu. Utworzenie i wykonanie wątku jest znacznie prostsze niż utworzenie procesu, więc jeśli zachodzi potrzeba realizacji kilku równoległych akcji w jednym programie, wykorzystywane są dodatkowe wątki. W JVM dowolny program działa w głównym wątku, a reszta jest z niego uruchamiana. W ramach tego samego procesu wątki mogą wymieniać między sobą dane. Rozpoczynając nowy wątek, możesz zadeklarować go jako wątek użytkownika za pomocą metody
setDaemon(true);
takie wątki zostaną automatycznie zakończone, jeśli nie ma już innych uruchomionych wątków. Wątki mają priorytet pracy (wybór priorytetu nie gwarantuje, że wątek o najwyższym priorytecie zakończy się szybciej niż wątek o niższym priorytecie).
- MIN_PRIORITY
- NORM_PRIORITY (domyślnie)
- MAX_PRIORITY
Podstawowe metody pracy ze strumieniami:
run()
– wykonuje wątek
start()
– rozpoczyna wątek
getName()
– zwraca nazwę wątku
setName()
– określa nazwę strumienia
wait()
– metoda dziedziczona, wątek czeka na wywołanie metody notify()
z innego wątku
notify()
– metoda dziedziczona, wznawia zatrzymany wcześniej wątek
notifyAll()
– metoda dziedziczona, wznawia wcześniej zatrzymane wątki
sleep()
– wstrzymuje transmisję na określony czas
join()
– czeka na zakończenie wątku
interrupt()
– przerywa wykonywanie wątku
Więcej metod znajdziesz
tutaj.Czas pomyśleć o nowych wątkach jeśli Twój program posiada:
- Dostęp do sieci
- Dostęp do systemu plików
- graficzny interfejs użytkownika
Klasa wątku
Wątki w Javie są reprezentowane jako klasa
Thread
i jej potomkowie. Poniższy przykład jest prostą implementacją klasy stream.
import static java.lang.System.out;
public class ExampleThread extends Thread{
public static void main(String[] args) {
out.println("Основной поток");
new ExampleThread().start();
}
@Override
public void run() {
out.println("Новый поток");
}
}
W rezultacie otrzymujemy
Основной поток
Новый поток
Tutaj tworzymy naszą klasę i czynimy ją potomkiem klasy
Thread
, po czym piszemy metodę main(), aby uruchomić główny wątek i zastąpić metodę
run()
klasy
Thread
. Teraz, po utworzeniu instancji naszej klasy i wykonaniu jej odziedziczonej metody,
start()
uruchomimy nowy wątek, w którym zostanie wykonane wszystko, co opisano w ciele metody
run()
. Brzmi skomplikowanie, ale patrząc na przykładowy kod, wszystko powinno być jasne.
Uruchomialny interfejs
Oracle sugeruje również zaimplementowanie interfejsu w celu uruchomienia nowego wątku
Runnable
, co daje nam większą elastyczność projektowania niż jedyne dostępne dziedziczenie w poprzednim przykładzie (jeśli spojrzysz na źródło klasy,
Thread
zobaczysz, że implementuje ono również interfejs
Runnable
). Skorzystajmy z zalecanej metody tworzenia nowego wątku.
import static java.lang.System.out;
public class ExampleRunnable implements Runnable {
public static void main(String[] args) {
out.println("Основной поток");
new Thread(new ExampleRunnable()).start();
}
@Override
public void run() {
out.println("Новый поток");
}
}
W rezultacie otrzymujemy
Основной поток
Новый поток
Przykłady są bardzo podobne, ponieważ Pisząc kod musieliśmy zaimplementować abstrakcyjną metodę
run()
opisaną w interfejsie
Runnable
. Zakładanie nowego wątku wygląda trochę inaczej. Stworzyliśmy instancję klasy,
Thread
przekazując jako parametr referencję do instancji naszego interfejsu
Runnable
. To podejście pozwala na tworzenie nowych wątków bez bezpośredniego dziedziczenia klasy
Thread
.
Długie operacje
Poniższy przykład wyraźnie pokaże korzyści płynące z używania wielu wątków. Powiedzmy, że mamy proste zadanie, które wymaga kilku długich obliczeń. Przed tym artykułem rozwiązalibyśmy je metodą,
main()
być może dzieląc je na osobne metody dla ułatwienia percepcji, może nawet na klasy, ale istota byłaby taka sama. Wszystkie operacje będą wykonywane sekwencyjnie, jedna po drugiej. Zasymulujmy obliczenia o dużej wadze i zmierzmy czas ich wykonania.
public class ComputeClass {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 1");
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 2");
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 3");
long timeSpent = System.currentTimeMillis() - startTime;
System.out.println("программа выполнялась " + timeSpent + " миллисекунд");
}
}
W rezultacie otrzymujemy
complete 1
complete 2
complete 3
программа выполнялась 9885 миллисекунд
Czas wykonania pozostawia wiele do życzenia i przez cały ten czas patrzymy na pusty ekran wyjściowy, a sytuacja jest bardzo podobna do historii o wujku Petyi, tyle że teraz w jego roli my, twórcy, nie skorzystaliśmy wszystkie możliwości nowoczesnych urządzeń. Poprawimy się.
public class ComputeClass {
public static void main(String[] args) {
long startTime = System.currentTimeMillis();
new MyThread(1).start();
new MyThread(2).start();
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete 3");
long timeSpent = System.currentTimeMillis() - startTime;
System.out.println("программа выполнялась " + timeSpent + " миллисекунд");
}
}
class MyThread extends Thread{
int n;
MyThread(int n){
this.n = n;
}
@Override
public void run() {
for(double i = 0; i < 999999999; i++){
}
System.out.println("complete " + n);
}
}
W rezultacie otrzymujemy
complete 1
complete 2
complete 3
программа выполнялась 3466 миллисекунд
Czas działania został znacznie skrócony (efekt ten może nie zostać osiągnięty lub może nawet wydłużyć czas wykonania na procesorach nie obsługujących wielowątkowości). Warto zaznaczyć, że wątki mogą kończyć się nie po kolei, a jeśli programista potrzebuje przewidywalności działań, musi ją zaimplementować niezależnie dla konkretnego przypadku.
Grupy wątków
Wątki w Javie można łączyć w grupy; do tego służy klasa
ThreadGroup
. Grupy mogą obejmować zarówno pojedyncze wątki, jak i całe grupy. Może to być wygodne, jeśli trzeba przerwać przepływy związane na przykład z siecią w przypadku utraty połączenia. Więcej o grupach możesz przeczytać
tutaj. Mam nadzieję, że teraz temat stał się dla Ciebie jaśniejszy i Twoi użytkownicy będą zadowoleni.
GO TO FULL VERSION