Problemy rozwiązane przez wielowątkowość
W rzeczywistości wielowątkowość została wynaleziona, aby osiągnąć dwa ważne cele:-
Rób kilka rzeczy jednocześnie.
W powyższym przykładzie różne wątki (członkowie rodziny) wykonywały równolegle kilka czynności: zmywały naczynia, szły do sklepu, pakowały rzeczy.
Możemy podać przykład ściślej związany z programowaniem. Załóżmy, że masz program z interfejsem użytkownika. Po kliknięciu przycisku „Kontynuuj” w programie powinny nastąpić pewne obliczenia, a użytkownik powinien zobaczyć następujący ekran. Gdyby te czynności były wykonywane sekwencyjnie, program po prostu zawiesiłby się po kliknięciu przez użytkownika przycisku „Kontynuuj”. Użytkownik będzie widział ekran z przyciskiem „Kontynuuj”, dopóki program nie wykona wszystkich wewnętrznych obliczeń i nie dojdzie do części, w której następuje odświeżenie interfejsu użytkownika.
Cóż, myślę, że poczekamy kilka minut!
Albo możemy przerobić nasz program lub, jak mówią programiści, „zrównoleglić” go. Wykonajmy nasze obliczenia na jednym wątku i narysujmy interfejs użytkownika na innym. Większość komputerów ma wystarczającą ilość zasobów, aby to zrobić. Jeśli pójdziemy tą drogą, program nie zawiesi się, a użytkownik będzie płynnie przechodził między ekranami, nie martwiąc się o to, co dzieje się w środku. Jedno nie koliduje z drugim :)
-
Szybciej wykonuj obliczenia.
Tutaj wszystko jest znacznie prostsze. Jeśli nasz procesor ma wiele rdzeni, a większość dzisiejszych procesorów tak ma, kilka rdzeni może równolegle obsłużyć naszą listę zadań. Oczywiście, jeśli musimy wykonać 1000 zadań, z których każde zajmuje jedną sekundę, jeden rdzeń może zakończyć listę w 1000 sekund, dwa rdzenie w 500 sekund, trzy w nieco ponad 333 sekundy itd.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("I'm Thread! My name is " + getName());
}
}
Aby tworzyć i uruchamiać wątki, musimy utworzyć klasę, aby odziedziczyła java.lang . Thread i zastąp jego metodę run() . To ostatnie wymaganie jest bardzo ważne. To właśnie w metodzie run() definiujemy logikę wykonywania naszego wątku. Teraz, jeśli utworzymy i uruchomimy instancję MyFirstThread , metoda run() wyświetli wiersz z nazwą: metoda getName() wyświetli „systemową” nazwę wątku, która jest przypisywana automatycznie. Ale dlaczego mówimy niepewnie? Stwórzmy jeden i przekonajmy się!
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
Wyjście konsoli: Jestem Thread! Nazywam się Wątek-2 Jestem Wątkiem! Nazywam się Wątek-1 Jestem Wątkiem! Nazywam się Wątek-0 Jestem Wątkiem! Nazywam się Nici-3 Jestem Nicią! Nazywam się Nici-6 Jestem Nicią! Nazywam się Nici-7 Jestem Nicią! Nazywam się Nici-4 Jestem Nicią! Nazywam się Thread-5 Jestem Thread! Nazywam się Nici-9 Jestem Nicią! Nazywam się Thread-8 Stwórzmy 10 wątków ( obiekty MyFirstThread , które dziedziczą Thread ) i uruchommy je, wywołując metodę start() na każdym obiekcie. Po wywołaniu metody start() wykonywana jest logika w metodzie run() . Uwaga: nazwy wątków nie są w kolejności. Dziwne, że nie po kolei:, Wątek-1 , Wątek-2 i tak dalej? Tak się składa, że jest to przykład czasu, w którym myślenie „sekwencyjne” nie pasuje. Problem polega na tym, że udostępniliśmy tylko polecenia do tworzenia i uruchamiania 10 wątków. Harmonogram wątków, specjalny mechanizm systemu operacyjnego, decyduje o kolejności ich wykonywania. Jego precyzyjny projekt i strategia podejmowania decyzji to tematy do głębokiej dyskusji, w którą nie będziemy się teraz zagłębiać. Najważniejszą rzeczą do zapamiętania jest to, że programista nie może kontrolować kolejności wykonywania wątków. Aby zrozumieć powagę sytuacji, spróbuj jeszcze kilka razy uruchomić metodę main() z powyższego przykładu. Wyjście konsoli przy drugim uruchomieniu: Jestem Nici! Nazywam się Wątek-0 Jestem Wątkiem! Nazywam się Nici-4 Jestem Nicią! Nazywam się Nici-3 Jestem Nicią! Nazywam się Wątek-2 Jestem Wątkiem! Nazywam się Wątek-1 Jestem Wątkiem! Nazywam się Thread-5 Jestem Thread! Nazywam się Nici-6 Jestem Nicią! Nazywam się Thread-8 Jestem Nicią! Nazywam się Nici-9 Jestem Nicią! Nazywam się Thread-7 Wyjście konsoli z trzeciego uruchomienia: Jestem Thread! Nazywam się Wątek-0 Jestem Wątkiem! Nazywam się Nici-3 Jestem Nicią! Nazywam się Wątek-1 Jestem Wątkiem! Nazywam się Wątek-2 Jestem Wątkiem! Nazywam się Nici-6 Jestem Nicią! Nazywam się Nici-4 Jestem Nicią! Nazywam się Nici-9 Jestem Nicią! Nazywam się Thread-5 Jestem Thread! Nazywam się Nici-7 Jestem Nicią! Nazywam się Wątek-8
Problemy stworzone przez wielowątkowość
W naszym przykładzie z książkami widzieliście, że wielowątkowość rozwiązuje bardzo ważne zadania i może przyspieszyć nasze programy. Często wielokrotnie szybciej. Ale wielowątkowość jest uważana za trudny temat. Rzeczywiście, jeśli jest używany niewłaściwie, stwarza problemy zamiast je rozwiązywać. Kiedy mówię „stwarza problemy”, nie mam na myśli jakiegoś abstrakcyjnego sensu. Istnieją dwa specyficzne problemy, które może powodować wielowątkowość: impas i warunki wyścigu. Zakleszczenie to sytuacja, w której wiele wątków czeka na zatrzymane przez siebie zasoby i żaden z nich nie może dalej działać. Porozmawiamy o tym więcej na kolejnych lekcjach. Na razie wystarczy następujący przykład:
- Wątek-1 przestaje wchodzić w interakcje z Obiektem-1 i przełącza się na Obiekt-2, gdy tylko Wątek-2 przestaje wchodzić w interakcje z Obiektem-2 i przełącza się na Obiekt-1.
- Wątek-2 przestaje wchodzić w interakcje z Obiektem-2 i przełącza się na Obiekt-1, gdy tylko Wątek-1 przestaje wchodzić w interakcje z Obiektem-1 i przełącza się na Obiekt-2.
public class MyFirstThread extends Thread {
@Override
public void run() {
System.out.println("Thread executed: " + getName());
}
}
public class Main {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
MyFirstThread thread = new MyFirstThread();
thread.start();
}
}
}
Teraz wyobraź sobie, że program jest odpowiedzialny za uruchomienie robota, który gotuje jedzenie! Thread-0 wyciąga jajka z lodówki. Wątek-1 włącza piec. Thread-2 bierze patelnię i stawia ją na kuchence. Wątek-3 zapala piec. Wątek-4 wlewa olej do miski. Thread-5 rozbija jajka i wlewa je na patelnię. Wątek-6 wyrzuca skorupki jajek do kosza. Nici-7 usuwa ugotowane jajka z palnika. Nici-8 kładzie ugotowane jajka na talerzu. Nici-9 zmywa naczynia. Spójrz na wyniki naszego programu: Wątek wykonany: Wątek-0 Wątek wykonany: Wątek-2 Wątek wykonany Wątek-1 Wątek wykonany: Wątek-4 Wątek wykonany: Wątek-9 Wątek wykonany: Wątek-5 Wątek wykonany: Wątek-8 Wątek wykonany: Wątek-7 Wątek wykonany: Wątek-3 Czy to rutyna komediowa? :) A wszystko dlatego, że działanie naszego programu zależy od kolejności wykonywania wątków. Przy najmniejszym naruszeniu wymaganej kolejności nasza kuchnia zamienia się w piekło, a szalony robot niszczy wszystko wokół. Jest to również częsty problem w programowaniu wielowątkowym. Usłyszysz o tym nie raz. Na zakończenie tej lekcji chciałbym polecić książkę o wielowątkowości. 
GO TO FULL VERSION