Techniki Zaawansowane
8 godz. 35 min · Java · Full-stack i Programowanie
Rafał SolarskiW poczatkowych lekcjach przyjrzymy sie pokladowi, na jakim bedziemy uruchamiali nasze programy. Wykorzystamy VisualVM do podgladania parametrów JVM. Zobaczymy równiez jakie zasoby sa tam dostepne oraz dowiesz sie jak monitorowac to, czy dobrze z nich korzystamy. Opowiemy Ci tez miedzy innymi o tym, jak zrobic heap i thread dump, oraz jak wlaczyc logi GC.
Kolejne lekcje kursu zostaly poswiecone wyrazeniom regularnym. Wyrazenia regularne mozemy wykorzystac nie tylko do walidacji danych wprowadzania przez uzytkownika, ale równiez do dzielenia tekstu oraz wyluskiwania wystapien wzorców w duzym tekscie. W tym rozdziale dowiemy sie miedzy innymi tego, jak mozemy wykorzystac ten potencjal z poziomu Javy.
Dzieki typom generycznym jestesmy w stanie osiagnac bezpieczenstwo typów w trakcie kompilacji przy zachowaniu elastycznosci pisanego kodu. W nastepnym rozdziale kursu przyjrzymy sie temu, jak mozemy je wykorzystac, oraz w jaki sposób czasami nalezy z tego bezpieczenstwa zrezygnowac.
Interfejsy funkcyjne to sposób na przeniesienie odrobiny swiata programowania funkcyjnego do Javy. Gdy polaczymy je z wyrazeniami lambda oraz API strumieni pozwola nam pisac te sama logike w duzo bardziej przejrzysty sposób.
Kolekcje, czyli listy, mapy, zbiory i kolejki to jedne z najczesciej wykorzystywanych klas w codziennej pracy. To jak dobrze poznamy ich mozliwosci ma ogromny wplyw na to, w jaki sposób bedziemy podchodzili do rozwiazywania wyzwan na naszej drodze.
System plików to zasób bez którego ciezko sobie poradzic. Pozwalaja nam na dostarczanie konfiguracji, danych wejsciowych i wyjsciowych, a przez to równiez integrowanie ze soba calych systemów.W tym rozdziale nauczymy sie korzystac z tych dobrodziejstw w bezpieczny sposób wykorzystujac m.in. IO Streams.
Trzymanie daty w String to niekoniecznie najwygodniejszy sposób. Juz od pewnego czasu Java dysponuje bardzo wygodnym Date&Time API - zobaczymy, co mozemy w nim znalezc.
Wielowatkowosc nie jest prosta... i dlugo tak jeszcze pozostanie. Nawet jesli korzystamy z frameworków, które próbuja ja przed nami ukryc, to ciagle musimy byc swiadomi problemów jakie sie z nia wiaza. W tym rozdziale poznamy glówne problemy na jakie mozna natrafic programujac wielowatkowo w Javie. Poznamy równiez klasy, które zdecydowanie pomoga nam zapanowac nad ta zlozonoscia.
JDBC to najbardziej podstawowy sposób laczenia sie z baza SQL z poziomu Javy. W wielu systemach/aplikacjach stosuje sie rozwiazania ORM takie jak JPA/Hibernate. Mimo wszystko warto wiedziec jak to pod spodem dziala oraz umiec poradzic sobie w aplikacjach, gdzie wybrano bardziej "lekkie" podejscie niz Hibernate.
Na koniec kursu wykorzystamy zdobyta wiedze, aby stworzyc prosta aplikacje pozwalajaca na przechowywanie danych o wydatkach. Po stworzeniu coreu aplikacji, mozesz spróbowac uzupelnic go za pomoca GUI.
Ten kurs stworzony zostal przede wszystkim z mysla o osobach, które juz poznaly podstawy jezyka, takie jak zmienne, mechanizmy kontroli wykonania, klasy, typy generyczne.Jezeli chcesz poczuc sie swobodnie nie tylko jesli chodzi o mechanizmy jezyka, ale równiez pod wzgledem znajomosci standardowej biblioteki Javy - to kurs w sam raz dla Ciebie. Materialy beda przydatne dla studentów, którzy znaja juz podstawowa skladnie Javy; programistów Javy chcacych poszerzyc lub uporzadkowac swoja wiedze; programistów innych jezyków, którzy chca poznac inny stack technologiczny.
Cześć w tej lekcji pokażę ci jak możesz wykorzystać
executor service oraz feature'y do pracy z wielowątkowością
tutaj mamy taki przykład biznesowy mamy jakieś wejście powiedzmy
tutaj wpada nam jakiś request od użytkownika i musimy wysłać
maila tak może coś tam jeszcze się dzieje no i potem jeszcze robimy
response to client tutaj dalej już jest zwrotka do użytkownika
czy to jest powiedzmy zwrócenie jakiegoś widoku jakiegoś powiadomienia coś takiego jeżeli
teraz to sobie uruchomimy tak to pod spodem send mail po prostu
oczekuje jakiś kawałek na koniec printuje tak pod spodem po prostu
przez chwilę czeka tak nie róbmy tego w domu nie róbmy sleep nigdy w kodzie
produkcyjnym a tutaj na potrzeby przykładu to jest okej no i powiedzmy tutaj mamy
jakiś odczyt ile to trwało w tym momencie dwie
sekundy prawda okej i wtedy jest response okej
i załóżmy sobie że nie chcemy czekać na żadne potwierdzenie
że ten mail został wysłany tak możemy od razu zwrócić do
użytkownika jakiś komunikat tak od razu możemy to zrobić a tak
naprawdę możemy gdzieś w tle wysłać tego maila on się może wysłać nie wysłać jeżeli się nie wyśle
to niech napisze coś w logach na przykład prawda no czyli oczywiście jeżeli chcemy żeby
cała ta operacja trwała krócej no to wystarczy wysłać to tak ten kawałek do
jakiegoś innego wątku prawda możemy to zrobić tak jak było pokazywane w poprzedniej
lekcji czyli stworzyć po prostu threada i zrobić na nim start tak tutaj przenieść
tę operację dalej ok i teraz mam po prostu 20
milisekund tak czyli później jest wysyłany ten mail send tak
po jakimś czasie i mamy od razu response to client zanim w ogóle co się wydarzy
tak zanim zostanie wysłany tak czyli oszczędzamy czas tak naprawdę okej i
tak zrobiliśmy poprzedniej lekcji to tylko z tym podejściem wiąże
się kilka problemów pierwszą rzeczą którą powinniśmy sprawdzić jeżeli
wysyłamy jakąś operację do drugiego wątku tak wrzucałem mu jakiś kod to
czy to co jest robione jest thread safety to znaczy
cały ten obiekt który tutaj wykorzystujemy tak jeżeli on nie jest w całości
cały cykl jego życia wykonywany w innym wątku i na przykład został
złożony później powiedz mi jakieś wywołania były tylko w jednym wątku tym
drugim tak a tutaj jest taka sytuacja że on powiedzmy tworzymy
go i później na tym mail senderze może kilka wątków korzystać może
być wywołany tutaj ale może być równie dobrze z tego wątku wywołany no
to wszystko co tam byśmy robili musi być trade safety co to dokładnie
znaczy i jak to osiągnąć dowiem się w kolejnych lekcjach a to jest pierwsze
tak jakby zaznaczenie co tu się musi dziać drugą rzeczą jest to że
ten wątek który powiedzmy tutaj tworzymy no
to on będzie tworzony dla każdego requestu który nam tutaj wpadnie tak czyli
powiedzmy dostaniemy teraz dostaniemy na przykład sytuację kiedy dostajemy request
http od użytkownika tak czy jakieś inne wywołanie czy on klika przycisk i
za każdym razem on wchodzi tam do tej metody i tworzy nam tutaj wątek
tak new thread i od razu start robi no to jeżeli ją kliknie 200 razy od
razu tak w ciągu sekundy no to dostaniemy 200 wątków w tym momencie
na każdej platformie może być ograniczenie ile
tamtych wątków stworzonych ale nawet nie ma takiego ograniczenia
to stworzenie dużej liczby wątków może sprawić że nasza aplikacja po prostu się
zwyczajnie zapcha tak trzecią rzeczą jest to że jeżeli
tworzymy te wątki tak za każdym razem tak no
to tracimy trochę czasu zanim one się stworzą tak nie jest duży kwant
czasu tak ale jednak trochę no i moglibyśmy
w sumie wykorzystywać te wątki w tym momencie jeżeli ten
kod się wykona tak to ten wątek jest do sprzątnięcia przez garbage
collector tak prawda w tym momencie no a są podejścia które
pozwalają na wykorzystanie tego ostatnią rzeczą jest to że moglibyśmy
gdzieś na przykład zaczekać aż on wróci tak tu akurat tego nie potrzebujemy ale
mogą być takie wywołania na przykład że chcemy coś od jakiegoś serwisu i on
nam asynchronicznie to zwraca tak to w tym momencie tutaj byśmy musieli mieć jakiś kod który nam
modyfikuje naszą zmienną globalną to byśmy sobie tutaj utworzyli tak no
więc współdzielona zmienna wielowątkowości wolelibyśmy raczej tego uniknąć
okej część tych problemów jesteśmy w stanie rozwiązać
wykorzystując executor service ten executor service jest pewnym obiektem
który abstrachuje przed nami od tego w jaki sposób jest wykonywany
ten kod któremu przekazujemy tak on na przykład za nas
może tworzyć thread'y on nawet może już mieć te thread'y po poprzednich
wywołaniach i my mu tylko mówimy wywołaj mi taką operację wywołaj
mi takiego runnable tak czy coś to nawet jakiegoś callable
jeżeli chcemy jeżeli chcemy coś jeszcze zwrócić tak no
i on to po prostu robi i on może właśnie trzymać pewną tak zwany thread pool
czyli pewną pulę wątków która powiedzmy może być reużywana
z jednego miejsca czy z wielu miejsc w aplikacji i nie mamy takiego powiedzmy
problemu z tym że mamy nagle sto wątków bo mieliśmy 100 requestów tylko
po prostu te requesty nam się kolejkują tak kolejne wywołania okej i
wykorzystajmy sobie teraz executor service z naszego przykładu może to wyglądać
w ten sposób tworzymy sobie nasz executor service za chwilę jak
on to jest tworzony to ja do tego wrócę i później po prostu wywołamy execute i execute
nam zwraca void tak nic nie zwraca po prostu wykonuje
tą operację w tle gdzieś tak tutaj możemy zrobić domknięcie przekazać
mail sender tak do tego jeszcze wrócimy okej i
w każdym razie ta operacja jest wywołana gdzieś na tej puli wątków którą tutaj stworzyliśmy
i teraz przyjrzyjmy się temu zasadniczo jeżeli potrzebujemy jakiegoś executor service
no to zazwyczaj będziemy wykorzystywali sobie klasy executors
która ma wiele takich metod fabrykujących statycznych tak które
nam po prostu stworzą te executor service i teraz my tworzymy sobie
new fixed thread pool jest to po prostu executor service
który pod spodem ma fixed thread pool czyli powiedzmy poole
wątków która ma stałą liczbę tych wątków w tym momencie 4 na
których są wykonywane operacje które my tutaj powiedzmy zrobimy execute sobie
z nimi na tym executor service prawda okej i
teraz kilka słów o tym o tej liczbie którą my tutaj przekazujemy ta
liczba jakby możemy sobie utworzyć kilka typów pool'i
jest pierwsza rzecz i możemy trochę majstrować przy tych liczbach
liczbie threadów które do nich wrzucamy w
zależności od tego po pierwsze ile mamy naszych cpu
ile mamy powiedzmy jeszcze core'ów w tym cpu ile one są w stanie obsłużyć wątków
to jest jedna rzecz a druga rzecz czy trochę od charakterystyki tego
co my będziemy wywoływali na tych thread pool'ach tak dlatego
że powiedzmy inną liczbę thread'ów możemy mieć w momencie kiedy wykorzystujemy
takie operacje które są bardzo takie czyli powiedzmy robią
dużo wejścia wyjścia na przykład czytają z plików zapisują do plików czy czekają
na jakiś socket na przykład powiedzmy z jakiegoś interfejsu sieciowego tak czyli powiedzmy
robią jakieś operacje na sieci no i one mogą mieć dużo tych wątków
z tego względu że to że one czekają na jakieś io tak to nie blokuje
w tym momencie cpu i możemy mieć jeszcze takie te cpu bound
operacje które powiedzmy robią dużo obliczeń na przykład wyliczają
jakiś sumy jakieś obliczenia tak czyli powiedzmy jakieś takie na przykład obliczenia
rozporoszone na jakichś flinkach sparkach i tak dalej i tam już nie powinniśmy mieć więcej
therad'ów tutaj utworzonych tak tym poolu niż
liczba wątków które jest w stanie obsłużyć nasze cpu i równocześnie minus
jeden jeszcze więc nie ma też nie zawsze
nasze operacje są takie że są full io bound i full powiedzmy cpu
bound więc dobranie tej liczby często wiąże się
powiedzmy z tym że widzimy że jest jakaś mała przepustowość naszej aplikacji
no to dodajemy sobie jakieś metryki które nam pokazują jakie są
na przykład opóźnienia na poszczególnych operacjach tak logujemy takie rzeczy no
i później możemy eksperymentować zwiększając te liczby zmniejszając i tak dalej i
ostatnią dobrą rzeczą którą możemy robić to jest rozdzielenie
thread pool'ów tak czyli możemy mieć na przykład właśnie 2 thread pool'y jeden będzie
dla wszystkich operacji które są na przykład io bound w naszej aplikacji a
drugi dla tych które są cpu bound dlatego wtedy możemy na przykład nie
zależnie od siebie dostrajać liczbę wątków w jednym i drugim poolu
prawda no okej no dobrze tutaj jak
widzimy sobie wywołamy execute i teraz jak odpalimy sobie ten przykład no
to działa identycznie tak jak wcześniej to robiliśmy prawda z
jedną różnicą a mianowicie powiedzmy tutaj sobie wywołaliśmy jakąś
operację tak tu widzimy mamy potwierdzenie że ten mail został wysłany wiemy też
że tutaj już doszliśmy do końca main'a i mamy response to client
prawda mimo to nasza aplikacja jeszcze się nie zamknęła z czego to
wynika pamiętasz jak ci mówiłem o daemon
thread'ach i takich zwykłych thread'ach dokładnie to
jest dokładnie tego samego wina w tym momencie jeżeli sobie tworzymy new fix
thread pool to domyślnie thread pool tutaj jest używany tworzy
sobie wątki które są non daemon tak czyli jeżeli one
jeszcze żyją bo powiedzmy jak my sobie wykonamy te operacje to pamiętaj że one tam
jeszcze czekają na wykonanie kolejnych execute'ów submit'ów
i takich operacji prawda więc no nasza aplikacja się zwyczajnie nie zamknie
z tego względu powinniśmy tutaj wszyscy wywołać shutdown na tym
executor service w tym momencie nam to się ładnie powiedzmy zakończy
tutaj mamy response to client jak został wykonany mail send to już nie było
więcej operacji do wykonania w tym executorze więc po
prostu on się zamknął te wątki które też były zostały zamknięte zostały
zakończone prawda no i po prostu cała apka się zamknęła w tym momencie bo nie było
żadnych non daemon thread'ów które jeszcze powiedzmy żyły okej teraz
jak działa to shutdown to shutdown tak naprawdę ustawia flagę
w tym pool'u która mówi nie przyjmuj mi więcej komend żądań
o to żeby coś obsłużyć czyli jak teraz byśmy zrobili jeszcze jedno execute tak
z czymś takim tutaj po tym shutdown to zobaczmy co teraz zostanie jeszcze
dostaniemy wyjątek o tym że jest rejected execution exception
tak ponieważ ten executor service do którego spróbowaliśmy
wrzucić tą operację jest już zamknięty pamiętaj jednak że to
że my tutaj blokujemy dodawanie kolejnych elementów tak
do kolejnych komend do tego nie sprawia że te komendy które obecnie są
wykonywane task'i tak nie sprawi to
że te taski zostaną przerwane w tym momencie prawda więc jeżeli mamy takie
trochę wredne te taski możemy trochę poczekać prawda nawet w nieskończoność
okej zróbmy trochę taką dłuższą dygresję odnośnie tego jak powinniśmy zamykać te poole
tutaj teraz jak węjdziemy sobie w send maila to widzimy że trochę zwiększyłem
długość tego oczekiwania i teraz jak sobie zrobimy tego shutdown'a no to oczywiście my
wyjdziemy tutaj z main'a no ale powiedzmy na tym się skończy tak bo ona i tak jeszcze
będzie czekał tam 1000 sekund tak może nie chcemy tak robić no bo
nie wiemy z punktu widzenia tego main'a powiedzmy kodu który zamyka tego
executor service my nie wiemy ile jeszcze może wisieć ta operacja tak
no chyba że my ją napisaliśmy i mamy jakieś powiedzmy hinty tak z tego
względu na ale możemy jeszcze zrobić coś takiego jak executor service czy możemy
await termination w tym momencie on czeka
po prostu tak shutdown po prostu przechodzi tam i ustawia tę flagę await termination jeszcze zaczeka
w tym momencie aż nam się zamknie tak ta pool'a tak on sam sam await
termination już nie robi shutdown'a więc musimy zawsze pamiętać żeby zrobić go
wcześniej no i teraz on będzie czekał tutaj jeszcze ustawiamy timeout
jaki on ma czekać oraz unit tak ile to jest tak i jeszcze
dostajemy to boolean'a czy po odczekaniu tego czasu udało
się zamknąć tego powiedzmy tego executor service może się okazać że się nie dało no
to co wtedy no i wtedy jeszcze możemy zrobić taką taką rzecz że zrobimy
jakieś executor service i zrobić shutdown now ale zasadniczo to shutdown
now mówi tak oczywiście one są zablokowane teraz nie
da się przejmować kolejnych tasków tak on zwraca wszystkie
te taski które powiedzmy jeszcze czekają w tej kolejce w tej kolejce które
powiedzmy zostały zeschedule'owane ale jeszcze nie zostały niezaczęte zostało wykonywanie
tych tasków a dla tych które już się wykonują w poolu to on
dla nich zazwyczaj będzie robił interact prawda okej
i teraz jeżeli nasz send tutaj obsługuje interrupt czyli musi robić taką
operację która powiedzmy sprawi że zostanie wyrzucony temat interrupted
teraz tak albo ma tu jakąś flagę w jakimś while tak jak widzieliśmy w poprzednim
przykładzie tak no to wtedy jest szansa że on się nam teraz zamknie prawda
okej i teraz po zmianie implementacji trochę send
mail jeszcze teraz obsługuje nam thread exception i w momencie kiedy złapie go to on robi
interrupt oczywiście i rzuca ten wyjątek czyli operacja zostanie przerwana w tym momencie no
jeżeli zrobimy shutdown now to teraz widzimy że zrobiliśmy shutdown
ale on nie poskutkował i od razu po tym zostało zrobione shutdown now nawet
nie ważne co się wydarzyło prawda no i powiedzmy mamy runtime exception
no i jest to jest to trochę lepsze niż gdybyśmy mieli czekać w nieskończoność aż powiedzmy ten
thread pool by się zamknął prawda tu jest taki problem że robimy shutdown
nie wiemy czy się wszystkie zamknęły i jeszcze może byśmy chcieli dać tu jakiś timeout
na przykład żeby zaczekać ileś zanim zaczniemy robić takie zamknięcie
powiedzmy brutalne z tym interrupt w środku prawda z tego względu nawet dokumentacja
javy proponuje inne zamknięcie tego thread'a tak tego executor
service który tutaj jest kawałek który powiedzmy ma wycięte jakieś komentarze
ale zasadniczo jest wzięty po prostu z dokumentacji w
tym miejscu i zasadniczo powiedzmy zobaczmy sobie co to robi to proponuję
taką sytuację że takie podejście że robimy shadow na początku tak bo wiemy
że nie chcemy obsługiwać żadnego więcej żadnego taska tak i później robimy
await termination czyli operację która nam blokuje z jakimś timeout'em koniecznie
tak bo nie chcemy że okej zablokował nam się jakiś pool no
to teraz my jeszcze w nieskończoność zablokujemy naszego main'a tak w jakieś miejsce jakąś inną inny
jakiś wątek który będzie czekał na to zamknięcie prawda więc tutaj robimy z jakimś
timeout'em robimy tego await termination a później robimy jeżeli to
się nie udało tak tutaj mamy if jeżeli to się nie udało no to robimy
bardziej brutalnie robimy shutdown now w tym momencie tak olewamy też
te taski które powiedzmy zostały byłyby tutaj zwrócone raczej no
raczej niewiele ich będzie tak w tym momencie olewamy te taski bo chcemy zamknąć
po prostu tak i później to shutdown now ono może też
się nie powieść tak powiedzmy zostało zrobienie ale mamy takie elementy
tak jakieś takie operacje które ciągle nam tutaj powiedzmy blokują tak
i tutaj w tym miejscu powinniśmy już tutaj na przykład poza
tym że koniecznie powinniśmy się zalogować to powinniśmy albo rzucić wyjątkiem tak bo
nasza aplikacja nasz jvm się zamknie jeżeli dostanie nie handlowany
wyjątek albo zrobić exit po prostu na aplikacji ponieważ w tym miejscu
już teraz jak zrobiliśmy shutdown na tym poolu nasza aplikacja
jest w state który jest no jest nieprawidłowy zasadniczo tak nie
jest możliwe że nie jest w stanie obsługiwać requestów które wpadają jakichś żądań
jakichś powiedzmy interakcji z użytkownikiem i tak dalej tak
a poza tym jeszcze blokuje ona się nie zamknęła do końca tak no to w
tym momencie rzucamy wyjątkiem albo po prostu zamykamy aplikację exit tak
oczywiście tutaj z tego względu że część z tych operacji jest blokujące
jak await termination no to tutaj jest oczywiście obsługa obowiązkowa interrupted exception
tutaj robimy jeszcze na wszelki wypadek shutdown na przykład jeżeli ten
wyjątek poleciał gdzieś tutaj tak na wszelki wypadek robimy shutdown now no
i oczywiście zawsze jeżeli obsługujemy iterrupted exception
to robimy jeszcze interrupt teraz jeżeli uruchomimy ten kawałek kodu na
początek oczywiście main cały przeszedł tak czyli to powiedzmy tak się zarejestrowało
tylko tutaj się zablokowaliśmy w tym momencie wysłanie maila powiedzmy
nie zdążymy z jego wysłaniem tak ponieważ jak tutaj zobaczymy jest 1000 sekund
tak i w tym momencie najpierw jest próba robienia
shutdown tak jak to się nie powiedzie to jest jeszcze jeszcze próba z awaitem i później na
koniec dopiero jeżeli await nie poskutkowało to jeszcze wymuszamy
zamknięcie przez zrobienie tego interrupted pod spodem tak no i powiedzmy
miejmy nadzieję że w tym akurat wypadku nam się ta aplikacja zamknie
ponieważ mamy ładną obsługę tak okej jak widzimy nasza aplikacja
się zamknęła przy okazji został rzucony wyjątek co prawda on został
tutaj wrzucony tak że po prostu zostało przerwane wykonanie tego ta taska w
tym miejscu tak ale nasz main skończył się normalnie w tym momencie prawda
oczywiście teraz możemy mieć jeszcze sytuację w której interrupted
nie jest obsługiwana albo nie ma takiej operacji która powiedzmy obsługuje
ten mechanizm interrupted w naszej w naszym tasku tutaj no i wtedy już niestety
jest no już w tym momencie powinniśmy rzucić wyjątkiem tutaj jeżeli nie jesteśmy w
stanie zamknąć aplikacji w dobry sposób tak taki zgrabny jeszcze
jednym trikiem który możemy tutaj użyć jest zrobienie threadów które
są daemon tak możemy dostarczyć przy tworzeniu naszego thread pool'a
w większości przypadków tak zwane thread factory tutaj widzimy przekazujemy taki obiekt który
po prostu tworzy new thread robi prawda on
tu dostaje kopa nawet te taski które są powiedzmy dodawane tak
no i w tym momencie możemy stworzyć nowego threada tak i mu ustawić
od razu set daemon dzięki temu nawet jeżeli nasza aplikacja powiedzmy się
zamyka tak to te thready które są w ramach tego poola tego jednego konkretnego
poola nie będą przeszkadzały w zamknięciu jvm tak więc tam mogą być jakieś
zblokowane właśnie taski tylko oczywiście wtedy trzeba odpowiednio zaczekać
najpierw pewnie prawda robimy ciągle await termianation no i jeżeli powiedzmy
po jakimś jakimś czasie to nic nie da tak no
to wtedy po prostu wychodzimy z naszego main'a czy wychodzimy z ostatniego wątku
który jest non daemon a który zajmuje się odpaleniem
i pewnie później zamknięciem naszej aplikacji no i po prostu zakończy
się nasz jvm w tym momencie tutaj możemy sobie zobaczyć jak to teraz zadziała odczekałem
po prostu 10 sekund i w tym momencie main się kończy aplikacja się kończy
ponieważ nie ma żadnych innych non daemon threadów podsumowując zamiast
wolnych threadów starajmy się używać executorów executor service możemy
je sobie wyciągać właśnie z executor właśnie wykorzytując metody fabrykujące powinniśmy starać
się rozdzielać thread pool na te które są io bound te które
są cpu bound i jeżeli one są wymieszane no to trudno trzymamy w jednym nie mamy takich specyficznych
mamy po prostu mieszane operacje poza tym jeżeli zamykamy
chcemy ładnie zamknąć tą aplikację tak zgrabnie tak graceful
shutdown stop no to staramy się wtedy wykorzystywać ten pattern
że najpierw oczekujemy na samodzielne zamknięcie pooli po zrobieniu shutdown tak czyli
powiedzmy po zablokowaniu dodawania nowych tasków a potem dopiero wywołać
shutdown now tak też z jakimś timeout'em i zrobić jakiś await prawda i wtedy możemy
sobie wyciągnąć z dokumentacji jak to dokładnie działa okej możemy też stworzyć
własne thread factory które stawia tworzone thready na daemon
true prawda i wtedy przynajmniej one nie blokują przy zamknięciu aplikacji niektóre
biblioteki tak jak guava na przykład dostarczają nam już takie gotowe egzekutory
które po prostu mają takiego thread factory w środku okej to wszystko co chciałem
ci pokazać na tę lekcje dzięki wielkie i już słyszymy się w kolejnym
odcinku