Zablokovanie Java Thread a Livelock

1. Prehľad

Aj keď podpora viacerých vlákien pomáha zlepšovať výkon aplikácie, prináša aj určité problémy. V tomto výučbe sa pomocou príkladov Java pozrieme na dva také problémy, zablokovanie a živé blokovanie.

2. Zablokovanie

2.1. Čo je zablokovanie?

Zablokovanie nastane, keď dve alebo viac vlákien večne čakajú na zámok alebo zdroj, ktorý drží iné z vlákien. V dôsledku toho sa môže aplikácia pozastaviť alebo zlyhať, pretože zablokované vlákna nemôžu postupovať.

Problém klasických jedálenských filozofov pekne demonštruje problémy so synchronizáciou v prostredí s viacerými vláknami a často sa používa ako príklad zablokovania.

2.2. Príklad zablokovania

Najprv sa pozrime na jednoduchý príklad Java, aby sme pochopili zablokovanie.

V tomto príklade vytvoríme dve vlákna, T1 a T2. Závit T1 hovory prevádzka1a vlákno T2 hovory operácie.

Ak chcete dokončiť svoje operácie, vlákno T1 potrebuje získať zámok1 najprv a potom zámok2, zatiaľ čo vlákno T2 potrebuje získať zámok2 najprv a potom zámok1. V zásade sa teda obe vlákna snažia získať zámky v opačnom poradí.

Teraz napíšeme Príklad zablokovania trieda:

verejná trieda DeadlockExample {private Lock lock1 = nový ReentrantLock (true); private Lock lock2 = new ReentrantLock (true); public static void main (String [] args) {DeadlockExample deadlock = new DeadlockExample (); new Thread (deadlock :: operation1, "T1"). start (); new Thread (deadlock :: operation2, "T2"). start (); } public void operation1 () {lock1.lock (); print ("zámok1 získaný, čaká sa na získanie zámku2."); spánok (50); lock2.lock (); print ("zámok2 získaný"); print ("vykonanie prvej operácie."); lock2.unlock (); lock1.unlock (); } public void operation2 () {lock2.lock (); print ("zámok2 získaný, čaká sa na získanie zámku1."); spánok (50); lock1.lock (); print ("zámok1 získaný"); print ("vykonanie druhej operácie."); lock1.unlock (); lock2.unlock (); } // pomocné metódy}

Teraz spustíme tento príklad zablokovania a všimneme si výstup:

Vlákno T1: zámok1 získaný, čaká sa na získanie zámku2. Vlákno T2: zámok2 získaný, čaká sa na získanie zámku1.

Po spustení programu vidíme, že program vedie k zablokovaniu a nikdy sa neopustí. Protokol ukazuje toto vlákno T1 čaká na zámok2, ktorý je držaný niťou T2. Podobne niť T2 čaká na zámok1, ktorý je držaný niťou T1.

2.3. Vyhýbanie sa uviaznutiu

Zablokovanie je bežný problém súbežnosti v Jave. Preto by sme mali navrhnúť aplikáciu Java, aby sme sa vyhli potenciálnym zablokovaniu.

Na začiatok by sme sa mali vyhnúť nutnosti získavania viacerých zámkov pre vlákno. Ak však vlákno potrebuje viac zámkov, mali by sme sa ubezpečiť, že každé vlákno získava zámky v rovnakom poradí, aby vyhnúť sa akejkoľvek cyklickej závislosti pri získavaní zámku.

Môžeme tiež použiť časované pokusy o uzamknutie, ako tryLock metóda v Zamknúť rozhranie, aby sa zabezpečilo, že vlákno nebude nekonečne blokovať, ak nie je schopné získať zámok.

3. Livelock

3.1. Čo je Livelock

Livelock je ďalším problémom súbežnosti a je podobný zablokovaniu. V živom zámku, dve alebo viac vlákien pokračuje v prenose stavov medzi sebou namiesto nekonečného čakania, ako sme videli v príklade zablokovania. V dôsledku toho vlákna nie sú schopné vykonávať svoje príslušné úlohy.

Skvelým príkladom funkcie livelock je systém správ, kde v prípade výnimky spotrebiteľ správy vráti transakciu a vráti správu späť do čela frontu. Potom sa z frontu opakovane číta rovnaká správa, len aby sa spôsobila ďalšia výnimka a bola vrátená späť do fronty. Spotrebiteľ nikdy nevyzdvihne žiadnu ďalšiu správu z frontu.

3.2. Príklad živého zámku

Teraz, aby sme demonštrovali stav zablokovania, vezmeme si ten istý príklad zablokovania, o ktorom sme hovorili predtým. V tomto príklade tiež vlákno T1 hovory prevádzka1 a niť T2 hovory prevádzka2. Logiku týchto operácií však mierne zmeníme.

Obe vlákna potrebujú na dokončenie svojej práce dva zámky. Každé vlákno získa svoj prvý zámok, ale zistí, že druhý zámok nie je k dispozícii. Aby sa teda mohlo druhé vlákno dokončiť ako prvé, každé vlákno uvoľní svoj prvý zámok a pokúsi sa znovu získať oba zámky.

Ukážme živý zámok pomocou a LivelockPríklad trieda:

verejná trieda LivelockExample {private Lock lock1 = nový ReentrantLock (true); private Lock lock2 = new ReentrantLock (true); public static void main (String [] args) {LivelockExample livelock = nový LivelockExample (); new Thread (livelock :: operation1, "T1"). start (); new Thread (livelock :: operation2, "T2"). start (); } public void operation1 () {while (true) {tryLock (lock1, 50); print ("zámok1 získaný, pokus o získanie zámku2."); spánok (50); if (tryLock (lock2)) {print ("lock2 acquired."); } else {print ("nie je mozne ziskat lock2, uvolnenie lock1."); lock1.unlock (); ďalej; } print ("vykonanie prvej operácie."); prestávka; } lock2.unlock (); lock1.unlock (); } public void operation2 () {while (true) {tryLock (lock2, 50); print ("zámok2 získaný, pokus o získanie zámku1."); spánok (50); if (tryLock (lock1)) {print ("lock1 acquired."); } else {print ("nie je možné získať zámok1, uvoľnenie zámku2."); lock2.unlock (); ďalej; } print ("vykonanie druhej operácie."); prestávka; } lock1.unlock (); lock2.unlock (); } // pomocné metódy}

Teraz si ukážeme tento príklad:

Vlákno T1: zámok1 bol získaný, pokúša sa získať zámok2. Vlákno T2: zámok2 bol získaný, pokúša sa získať zámok1. Vlákno T1: nemôže získať zámok2, uvoľnenie zámku1. Vlákno T2: nemôže získať lock1, uvoľniť lock2. Vlákno T2: zámok2 získaný, pokus o získanie zámku1. Vlákno T1: zámok1 získaný, pokus o získanie zámku2. Vlákno T1: nemôže získať zámok2, uvoľnenie zámku1. Vlákno T1: zámok1 bol získaný, pokúša sa získať zámok2. Vlákno T2: nemôže získať lock1, uvoľniť lock2. ..

Ako vidíme v protokoloch, obe vlákna opakovane získavajú a uvoľňujú zámky. Z tohto dôvodu žiadne z vlákien nie je schopné dokončiť operáciu.

3.3. Vyhýbam sa Livelock

Aby sme sa vyhli zablokovaniu, musíme sa pozrieť na stav, ktorý blokovanie spôsobuje, a potom podľa toho navrhnúť riešenie.

Napríklad, ak máme dve vlákna, ktoré opakovane získavajú a uvoľňujú zámky, čo má za následok živý zámok, môžeme navrhnúť kód tak, aby sa vlákna znovu pokúsili získať zámky v náhodných intervaloch. Toto dá nitiam spravodlivú šancu získať potrebné zámky.

Ďalším spôsobom, ako sa postarať o problém so živosťou v príklade systému zasielania správ, o ktorom sme už hovorili skôr, je vložiť neúspešné správy do samostatného frontu na ďalšie spracovanie namiesto ich opätovného zaradenia do rovnakého frontu.

4. Záver

V tomto tutoriáli sme diskutovali o zablokovaní a zablokovaní. Pozreli sme sa tiež na príklady jazyka Java, aby sme ukázali každý z týchto problémov, a stručne sme sa zmienili o tom, ako sa im môžeme vyhnúť.

Úplný kód použitý v tomto príklade nájdete ako vždy na serveri GitHub.


$config[zx-auto] not found$config[zx-overlay] not found