CodeGym /Blog Java /Random-PL /Wielowątkowość w Javie
Autor
Oleksandr Miadelets
Head of Developers Team at CodeGym

Wielowątkowość w Javie

Opublikowano w grupie Random-PL
Cześć! Przede wszystkim gratulacje: dotarłeś do tematu wielowątkowości w Javie! To poważne osiągnięcie — przeszedłeś długą drogę. Ale przygotuj się: to jeden z najtrudniejszych tematów na kursie. I nie chodzi o to, że używamy tutaj skomplikowanych klas lub wielu metod: w rzeczywistości użyjemy mniej niż dwudziestu. Chodzi o to, że będziesz musiał nieco zmienić sposób myślenia. Wcześniej programy były wykonywane sekwencyjnie. Niektóre linijki kodu następowały po innych, niektóre metody następowały po innych i wszystko było w zasadzie jasne. Najpierw coś policzyliśmy, potem wyświetliliśmy wynik na konsoli, a potem program się zakończył. Aby zrozumieć wielowątkowość, lepiej myśleć w kategoriach równoległości. Zacznijmy od czegoś całkiem prostego: ) Wyobraź sobie, że twoja rodzina przeprowadza się z jednego domu do drugiego. Zebranie wszystkich książek będzie ważną częścią przeprowadzki. Zgromadziłeś wiele książek i musisz umieścić je w pudełkach. Obecnie jesteś jedyną dostępną osobą. Mama przygotowuje jedzenie, brat pakuje ubrania, a siostra poszła do sklepu. Sam sobie jakoś poradzisz. Prędzej czy później zadanie wykonasz samodzielnie, ale zajmie to dużo czasu. Jednak twoja siostra wróci ze sklepu za 20 minut i nie ma nic innego do roboty. Więc może do ciebie dołączyć. Zadanie się nie zmieniło: włożyć książki do pudełek. Ale jest wykonywany dwa razy szybciej. Dlaczego? Ponieważ praca odbywa się równolegle. Dwa różne „wątki” (ty i twoja siostra) wykonujecie to samo zadanie jednocześnie. A jeśli nic się nie zmieni, wtedy będzie ogromna różnica czasu w porównaniu z sytuacją, w której robisz wszystko sam. Jeśli brat wkrótce skończy pracę, może ci pomóc, a sprawy potoczą się jeszcze szybciej.

Problemy rozwiązane przez wielowątkowość

W rzeczywistości wielowątkowość została wynaleziona, aby osiągnąć dwa ważne cele:
  1. 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!

    Wielowątkowość w Javie: co to jest, jakie są jej zalety i typowe pułapki - 3

    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 :)

  2. 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.

Ale jak już przeczytałeś w tej lekcji, dzisiejsze systemy są bardzo inteligentne i nawet na jednym rdzeniu obliczeniowym są w stanie osiągnąć równoległość, a raczej pseudorównoległość, gdzie zadania są wykonywane naprzemiennie. Przejdźmy zatem do konkretów i poznajmy najważniejszą klasę w wielowątkowej bibliotece Javy — java.lang.Thread. Ściśle mówiąc, wątki Javy są reprezentowane przez instancje klasy Thread . Oznacza to, że aby utworzyć i uruchomić 10 wątków, potrzebujesz 10 instancji tej klasy. Napiszmy najprostszy przykład:

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: Wielowątkowość w Javie: co to jest, jakie są jej zalety i typowe pułapki - 4Wyobraź sobie, że Wątek-1 wchodzi w interakcję z jakimś Obiektem-1, a Wątek-2 wchodzi w interakcję z Obiektem-2. Ponadto program jest tak napisany, że:
  1. 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.
  2. 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.
Nawet bez głębokiego zrozumienia wielowątkowości można łatwo zauważyć, że nic się nie wydarzy. Nici nigdy nie zamienią się miejscami i będą czekać na siebie w nieskończoność. Błąd wydaje się oczywisty, ale w rzeczywistości taki nie jest. Można to łatwo zrobić w programie. W kolejnych lekcjach rozważymy przykłady kodu powodującego zakleszczenie. Nawiasem mówiąc, Quora ma świetny przykład z życia wzięty , który wyjaśnia, czym jest impasJest. „W niektórych stanach w Indiach nie sprzedają ci gruntów rolnych, chyba że jesteś zarejestrowanym rolnikiem. Jednak nie zarejestrują cię jako rolnika, jeśli nie masz ziemi rolnej”. Świetnie! Co możemy powiedzieć?! :) Porozmawiajmy teraz o warunkach wyścigu. Sytuacja wyścigu to błąd projektowy w wielowątkowym systemie lub aplikacji, gdzie działanie systemu lub aplikacji zależy od kolejności wykonywania części kodu. Pamiętaj, nasz przykład, w którym rozpoczęliśmy wątki:

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. Wielowątkowość w Javie: co to jest, jakie są jej zalety i typowe pułapki - 6Książka „Java Concurrency in Practice” została napisana w 2006 roku, ale nie straciła na aktualności. Poświęcony jest wielowątkowemu programowaniu w Javie — od podstaw po najczęstsze błędy i antywzorce. Jeśli pewnego dnia postanowisz zostać guru wielowątkowości, ta książka jest obowiązkową lekturą. Do zobaczenia na kolejnych lekcjach! :)
Komentarze
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION