czwartek, 29 grudnia 2016

Wprowadzenie do programowania współbieżnego. Java util.concurent review... 1



    Przetwarzanie współbieżne (ang. concurrent computing) – przetwarzanie oparte na współistnieniu wielu wątków lub procesów, operujących na współdzielonych danych. Wątki uruchomione na tym samym procesorze są przełączane w krótkich przedziałach czasu, co sprawia wrażenie, że wykonują się równolegle. - tyle z Wikipedii ...


    W tym artykule nie będę się skupiać na teorii, pokażę kilka podstawowych klas i interfejsów jakie programiści mają do swojej dyspozycji aby móc tworzyć programy z użyciem wątków. Oczywiście w początkowych lekcjach skupimy się na podstawowych klockach z których możemy tworzyć nasze programy, nie mniej jednak jak później zobaczymy niektóre wykorzystywane przez nas klasy, mechanizmy będą tylko tłem dla wiedzy jaką chcę przekazać. Java oferuje nam dużo bardziej wyszukane mechanizmy pozwalające na obsługę wątków, ich wykonywania, odbierania od nich zadań. Dodatkowo Java 8 wprowadza nowy interfejs CompletableFuture umożliwiający nam na programowanie reaktywne, tworząc kod bardziej czytelnym i zrozumiałym. Nasze api nie zawsze musi być napisane w tradycyjny sposób, mam na myśli klient, czeka na zakończenie wywołanej przez niego metody (blokujące api), możemy mieć api, które będzie nam zwracać wynik, o którym zostaniemy powiadomieni, a wtedy możemy wykonać pewną akcję, akcja ta zostanie wywołana niezwłocznie po zakończeniu zadania, lecz bez blokowania wątku, z którego została wywołana nasza metoda. Z programowaniem współbieżnym wiąże się, także inne pojęcie synchronizacji, które jest kluczowym elementem jeśli nasze wątki operują na dzielonym zasobie, taki zasób utożsamiany będzie z pojęciem sekcji krytycznej... ale to wszystko później. Na początku skupmy się na podstawach.


    Przykłady, które będą umieszczone na stronie są pisane z użyciem wersji kompilatora Java 8. Sam wstęp ma pokazać pewien zarys jeśli chodzi o pewien koncept programowania współbieżnego oraz od strony technicznej jak wygląda to po stronie języka.



Spójrzmy na definicję interfejsu z pakietu java.util

    Co prawda interfejs ten nie należy do pakietu java.util.concurent, ale nie da się go pominąć chcąc przybliżyć możliwości jakie oferuje nam Java w kontekście programowania współbieżnego, lub po prostu nazwijmy to programowania z użyciem wątków.

Spójrzmy na definicję interfejsu z pakietu java.util


    Jak widać od wersji Javy 8 jest on opatrzony w adnotację @FunctionalInterface co oznacza, że jest to interfejs funkcyjny posiadający jedną abstrakcyjną metodę.

    Interfejs ten powinien być implementowany przez klasę, która ma reprezentować wykonanie pewnej operacji w wątku. Operację jaką chcemy wykonać, albo inaczej zadanie jest implementacją metody run.

    Od wersji Java 8 nie musimy jawnie deklarować klasy, która ma swoją nazwę, zaś możemy utworzyć implementację interfejsu za pomocą klasy anonimowej. Przejdźmy zatem do przykładu. Stworzymy w nim dwie implementacje metody run z interfejsu Runnable.


    W linijce 10 utworzyliśmy implementację interfejsu z użyciem klasy anonimowej, a w linijce 18 mamy bardziej eleganckie rozwiązanie, które oferuje nam Java 8. Użyliśmy tutaj wyrażenia lambda, inaczej mówiąc jest to funkcja anonimowa, która może przyjąć jakiś argument i go zwrócić lub nie.

    Interfejs Runnable definiuje nam funkcję taką w postaci metody run, która nie przyjmuje żadnego argumentu oraz nie zwraca żadnego rezultatu. Możemy to rozumieć na naszym przykładzie, że po operatorze -> przekazujemy ciało metody, w skrócie funkcję, która ma się wykonać w wątku.


    Samo zadeklarowanie klasy implementującej interfejs Runnable nie wystarcza aby nasz kod uruchomił się w oddzielnym wątku, potrzebujemy klasy, która będzie reprezentować ten wątek.
Klasa Thread z pakietu java.lang w jednym z konstruktorów przyjmuje właśnie obiekt Runnable, którego metoda run zostanie wywołana przy starcie nowego wątku.





    Wątek w ramach JVM zawsze musi należeć do pewnej grupy wątków. Grupa wątków reprezentowana przez klasę java.lang.ThreadGroup jest tworzona przy starcie maszyny wirtualnej o domyślnej nazwie "system". Grupy są zorganizowane w drzewo służą logicznemu podziałowi wątków.




    Poniżej widzimy, że w przypadku kiedy nie określimy w konstruktorze Thread, do jakiej grupy ma być przydzielony wątek zostanie wywołana metoda init z parametrem null. W linijce 371 pobierany jest aktualny wątek wykonujący się w ramach metody init, a w linijce 385 zostaje określona grupa do, której zostanie przydzielony wątek.






    Takie grupowanie daje pewną elastyczność oraz umożliwia wprowadzenie pewnych nazwijmy polityk bezpieczeństwa. Każdy wątek może należeć do jednej grupy, oraz możemy sprawdzić czy dany wątek próbuje utworzyć wątek z innej grupy.

    Poniżej mamy przykład programu wypisujący z bieżącego wątku wszystkie grupy w hierarchii drzewa.



    W prosty sposób możemy napisać program wypisujący nazwy wątków działających w maszynie wirtualnej Java.

    Metoda printAllThreadsCurrentlyRunningInJVM  na początku znajduje korzeń drzewa, a potem wywołuje metodę enumerate, która wypełnia podaną przez nas tablicę.





    W następnej lekcji skupimy się na klasie Thread, na jej metodach na stanach w jakich może znajdować się wątek.

   Api pakietu java.util.concurent w wersji 7 (nie znalazłem nigdzie 8, a nie chce mi się bawić w malarza uml)












Reakcje:

0 komentarze:

Prześlij komentarz