Cieľom tohto článku je ťa oboznámiť s Java multithreading-om – viacvláknovým spracovaním v Jave, vysvetliť jeho princípy, výhody ale aj nevýhody.

Multitasking – čo to je

Multitasking umožňuje na jednom počítači súbeh viacerých aktivít súčasne. Napr. na počítači   bežia paralelne viaceré programy, kde programátor kompiluje kód a súčasne počúva hudbu a na pozadí prebieha aktualizácia operačného systému.

Multitasking môže byť založený na procesoch a vláknach (threads). Vlákna môžu byť súčasťou procesov a tie umožňujú vykonávanie časti toho istého programu (procesu) súbežne na počítači.

Thread vs Process – vlákna verzus proces

– vlákna zdieľajú ten istý adresný priestor
– réžia pri prepínaní medzi vláknami je zvyčajne menej náročná ako medzi procesmi
– nároky na komunikáciu medzi vláknami sú relatívne malé

graf rozdielov vlákien a procesov
Threads vs Processes (vlákna vs. procesy)

Multithreading – prečo ho používať?

V prostredí na jednom vlákne môže byť vykonávaná iba jedna úloha v tom istom čase. Príkladom v Jave môže byť main() metóda, kde sa tok programu vykonáva sekvenčne. V prípade, že sa čaká na vstup od používateľa, výpočtové cykly CPU sú zbytočne nevyužité. Pri viacvláknovom programe by sa však v takom čase mohla vykonávať na pozadí analýza alebo kontrola dát požívateľa.

Podobne pri jednovláknových (singlethread) programoch s grafickým rozhraním, ktoré využívajú to isté vlákno na časovo náročné výpočty na procesore môže dochádzať počas nich k zamŕzaniu grafického rozhrania a teda zníženiu komfortu používateľa.

Threads – vlákna

Vlákno v programe je vykonávané sekvenčne a nezávisle od ostatných. Počas vykonávania programu teda môže súčasne bežať množstvo vlákien zdieľajúce spoločný adresný (pamäťový) priestor a teda si môžu vymieňať údaje.

Java threads – vlákna v Jave

Na multithreading v Jave sa vzťahujú 3 koncepty:
1. vytváranie vlákien a poskytnutie kódu, ktorý bude vláknom vykonávaný
2. prístup k spoločným dátam a kódu cez synchronizáciu
3. prechod medzi rôznymi stavmi vlákna

Java main thread – hlavné vlákno

Po spustení Java programu sa vytvorí vlákno používateľa, aby sa vykonala main() metóda aplikácie. Toto vlákno sa nazýva aj ako hlavné vlákno. Ak sa počas vykonávania nevytvoria ďalšie vlákna, program sa ukončí po jej vykonaní. Ďalšie vlákna sa vytvárajú z hlavného vlákna a program ostáva bežať aj skončení hlavnej metódy pokiaľ sa všetky vlákna používateľa nedokončia.

Vytváranie vlákna

V Jave je vlákno reprezentované objektom triedy Thread. Vlákno sa dá vytvoriť jedným z dvoch spôsobov:
1.  Implementovaním rozhrania java.lang.Runnable
2. Dedením z triedy java.lang.Thread

Thread synchronization – synchronizácia vlákien

Vlákna dokážu zdieľať zdroje, pretože zdieľajú ten istý adresný priestor. Avšak existujú kritické situácie, kedy je nanajvýš vhodné, aby v jednom čase malo prístup k zdieľaným zdrojom iba jedno vlákno. Príkladom môže byť objednávanie lístkov na koncert v informačnom systéme, kedy medzi sebou súťažia zákazníci, pričom každý z nich sa snaží dostať, čo najlepšie miesto, avšak každý z lístkov si môže objednať iba jeden z nich. Takže v samotnom procese kúpy lístka musí mať k danému lístku prístup iba jeden zákazník.

Pravidlá synchronizácie vlákien

Vlákno musí získať zámok na zdieľaný zdroj, predtým ako ho začne využívať. Runtime systému zabezpečuje, že žiadne iné vlákno nemôže mať prístup k zdieľanému zdroju, ak už iné vlákno naň získalo zámok.

Ak vlákno nemôže okamžite získať zámok, je zablokované, to znamená, že musí čakať na zámok, kým sa stane dostupným. Ak vlákno opúšťa zdieľaný zdroj, runtime systému zabezpečí odobratie zámku a ak nejaké vlákno čaká na prístup, toto môže získať prístup po získaní zámku.

Samotný program nemôže robiť žiadne závery o poradí vlákien, ktorým sa zámok poskytne, pretože o tom nerozhoduje programátor ale operačný systém. Bez správnej synchronizácie by dve alebo viaceré vlákna aktualizovali tu istú hodnotu súčasne a zanechali ju v nedefinovanom alebo nekonzistentnom stave.

Metódy synchronizácie

Ak je vlákno vo vnútri synchronizovanej metódy objektu, všetky ostatné vlákna, ktoré potrebujú rovnako vykonať kód zo synchronizovanej metódy musia čakať. Toto obmedzenie sa netýka vlákna, ktoré získalo zámok a vykonáva synchronizovanú metódu objektu.

Takáto metóda môže ďalej zavolať iné synchronizované metódy objektu bez toho aby bola zablokovaná. Nesynchronizované metódy objektu môžu byť zavolané hocikedy ostatnými vláknami.

Thread safety

Tento pojem sa používa na opis tried, ktoré boli navrhnuté tak, aby zabezpečovali stav svojich objektov vždy konzistentný a to aj vtedy ak sú objekty používané súčasne viacerými vláknami. Príkladom je trieda StringBuffer.

Java thread life cycle – životný cyklus vlákna v Jave

Na nasledovnom diagrame sú zobrazené všetky možne stavy, ktorými prechádzajú vlákna v Jave.

Stavový diagram vlákna
Stavový diagram vlákna

New – stav novovytvoreného vlákna, ktoré sa ešte nezačalo vykonávať.
Runnable – buď vykonávane, alebo pripravené na vykonanie, ale stále čaká na alokáciu zdrojov.
Blocked – čakajúce na získanie zámku na vstup do synchronizovanej metódy alebo bloku.
Waiting – čakajúce na iné vlákno, kým vykoná istú akciu, bez časového limitu.
Timed waiting – To isté ako waiting, ale s časovým limitom.
Terminated – vlákno dokončilo svoju exekúciu.

Stav čakania

Vlákno v stave čakania sa dá prebudiť, ak nastane jedna z týchto troch udalostí:
1. Iné vlákno zavolá metódu notify() na objekte čakajúceho vlákna a čakajúce vlákno sa vyberie ako vlákno, ktoré sa zobudí.
2. Čas čakania vlákna exspiruje.
3. Iné vlákno preruší čakajúce vlákno.

Stav oživenia

Zavolanie metódy notify() na objekte zobudí jedno z vlákien, ktoré čakajú na získanie zámku k danému objektu. Výber konkrétneho vlákna, ktoré za zobudí, je závislé od manažmentu vlákien implementovaných JVM. Prebudené vlákno nie je hneď v stave pripravené na vykonanie, ale najskôr zmení stav na získanie zámku. Vlákno je tiež odstránené zo zoznamu čakajúcich vlákien k danému objektu.

Stav exspirácie

Volanie metódy wait() špecifikuje čas, za ktorý dané vlákno exspiruje, ak nebude prebudené notifikáciou.

Stav prerušenia

Nastáva, ak iné vlákno vyvolá interrupt() metódu na čakajúcom vlákne. Prebudené vlákno je aktivované, ale návratová hodnota z wait() metódy skončí vyvolaním výnimky IntteruptedException, akonáhle prebudené vlákno príde na rad. Kód, ktorý túto výnimku vyvolá musí byť pripravený na jej ošetrenie.

Thread priorities – priority vlákien

Priority sú integer hodnoty od 1 (najnižšia priorita daná konštantou Thread.MIN_PRIORITY) do 10 (najvyššia priorita daná konštantou Thread.MAX_PRIORITY). Default hodnota je 5 (Thread.NORM_PRIORITY). Vlákno dedí svoju prioritu zo svojho rodičovského vlákna. Priorita vlákna sa da nastaviť použitím metódy setPriority() a načítať použitím metódy getPriority(), obe sú definované v triede Thread. Treba zdôrazniť, že nastavenie priority má pre JVM (Java Virtual Machine) odporúčací charakter a JVM sa ním nemusí riadiť.

Deadlock

Ide o situáciu, kde jedno vlákno čaká na zámok, ktoré drží druhé vlákno a to čaká na iný zámok, ktoré drží prvé vlákno.

Najdôležitejšie metódy triedy Thread

start() Inicializuje vlákno a zavolá interne metódu run().
run() Úloha, ktorá sa bude vykonávať, sa zadefinuje v tejto metóde.
sleep() Bežiaci proces sa na istý čas pozdrží.
yield() Zapauzuje vykonávanie aktuálneho vlákna a umožní vykonávanie čakajúcemu inému vláknu s rovnakou alebo vyššou prioritou.
stop() Zastaví vykonávanie vlákna natrvalo.
join() Vlákno bude čakať na dokončenie vykonávania iného vlákna.
isAlive() Skontroluje, či je vlákno ešte živé alebo už nie. Vráti booleovskú hodnotu (true/false), ktorá indikuje, či vlákno beží alebo nie.
setPriority() Nastavenie priority vlákna.

Výhody Java multithreadingu

Multithreading v Jave zlepšuje výkon a spoľahlivosť, zároveň drasticky znižuje čas potrebný na vykonanie programu a tým pádom sa znižuje prevádzkové náklady na softvér. Procesor a iné hardvérové zdroje sa tak využívajú efektívnejšie.

Nevýhody Java multithreadingu

Multithreading v Jave zvyšuje komplexnosť, čo sa týka ladenia kódu, zároveň sa zvyšuje pravdepodobnosť deadlocku pri vykonávaní procesov. Poradie vykonávania vlákien nie je v rukách programátora, takže každé spustenie programu môže mať iný priebeh. Komplikácie môžu nastať aj pri portovaní programu na iné platformy.

Multitasking vs Multithreading

Hlavným rozdielom medzi multitaskingom a multithreadingom je, že multitasking zahŕňa spustenie viacerých nezávislých procesov alebo úloh, zatiaľ čo multithreading zahŕňa rozdelenie jedného procesu na viacero vlákien, ktoré môžu vykonávať súčasne. Multitasking sa používa na spravovanie viacerých procesov, zatiaľ čo multithreading sa používa na zlepšenie výkonu jedného procesu.

Multitasking vs Multiprocessing

Rozdiel medzi multitaskingom a multiprocessingom je, že multitasking zahŕňa spustenie viacerých nezávislých procesov alebo úloh na jednom procesore, zatiaľ čo multiprocessing zahŕňa použitie viacerých procesorov na vykonávanie viacerých procesov súčasne. Multitasking sa používa na spravovanie viacerých procesov na jednom procesore, zatiaľ čo multiprocessing sa používa na zlepšenie výkonu systému tým, že umožňuje vykonávanie viacerých procesov súčasne na viacerých procesoroch.

Multiprogramming vs Multitasking vs Multithreading vs Multiprocessing

Pre správne zobrazenie na mobilnom zariadení si ťukni na obrázok tabuľky.

Multiprogramming Multitasking Multithreading Multiprocessing
Definícia Spustenie viacerých programov na jednom CPU Spustenie viacerých úloh (aplikácií) na jednom CPU Spustenie viacerých vlákien v rámci jednej úlohy (aplikácie) Spustenie viacerých procesov na viacerých procesoroch (alebo jadrách)
Zdieľanie zdrojov Prostriedky (CPU, pamäť) sú zdieľané medzi programami Zdroje (CPU, pamäť) sú zdieľané medzi úlohami Prostriedky (CPU, pamäť) sú zdieľané medzi vláknami Každý proces má svoju vlastnú sadu zdrojov (CPU, pamäť)
Plánovanie Na pridelenie času procesora programom používa plánovanie typu round-robin alebo na základe priority Na pridelenie času CPU úlohám používa plánovanie založené na prioritách alebo časovom segmentovaní Používa plánovanie založené na priorite alebo časovom segmentovaní na pridelenie času procesora vláknam Každý proces môže mať svoj vlastný plánovací algoritmus
Správa pamäte Každý program má svoj vlastný pamäťový priestor Každá úloha má svoj vlastný pamäťový priestor Vlákna zdieľajú pamäťový priestor v rámci úlohy Každý proces má svoj vlastný pamäťový priestor
Prepínanie kontextu Vyžaduje prepínanie kontextu na prepínanie medzi programami Vyžaduje prepnutie kontextu na prepínanie medzi úlohami Vyžaduje prepnutie kontextu na prepínanie medzi vláknami Vyžaduje prepnutie kontextu na prepínanie medzi procesmi
Medziprocesová komunikácia (IPC) Používa posielanie správ alebo zdieľanú pamäť pre IPC Používa posielanie správ alebo zdieľanú pamäť pre IPC Používa mechanizmy synchronizácie vlákien (napr. zámky, semafory) pre IPC Používa medziprocesové komunikačné mechanizmy (napr. potrubia, zásuvky) pre IPC

Ďalšie odporúčané zdroje

Tento článok mal za cieľ oboznámiť čitateľa s viacvláknovým spracovaním v Jave, vysvetliť jeho princípy, výhody ale aj nevýhody. Keďže ide o pomerne komplexnú tému dalo by sa o nej ešte veľa popísať a ísť ešte do väčšej hĺbky, ideálne s príkladmi. Preto na rozšírenie znalostí odporúčam tieto zdroje:

O autorovi

Jozef Wagner

Java Developer Senior

Viac ako 10 rokov programujem v Jave, momentálne pracujem v msg life Slovakia ako Java programátor senior a pomáham zákazníkom implementovať ich požiadavky do poistného softvéru Life Factory. Vo voľnom čase si rád oddýchnem v lese, prípadne si zahrám nejakú dobrú počítačovú hru.

Daj nám o sebe vedieť