Deadlock în java și metode de combatere a acesteia

Atunci când se dezvoltă aplicații cu mai multe fire, apare adesea o dilemă: ceea ce este mai important este fiabilitatea sau operabilitatea aplicației. De exemplu, folosim sincronizarea pentru siguranța firului, iar în cazul unei ordini de sincronizare incorecte putem provoca blocarea reciprocă. De asemenea, folosim bazine de fire și semafoare pentru a limita consumul de resurse, în timp ce o eroare în acest design poate duce la blocarea reciprocă din cauza lipsei de resurse. În acest articol, vom vorbi despre cum să evități blocarea reciprocă, precum și alte probleme legate de sănătatea aplicației. De asemenea, vom examina modul în care o cerere poate fi scrisă astfel încât să se poată recupera în cazurile de blocare reciprocă.






Impas - o situație în care două sau mai multe procese de a lua unele resurse, încercarea de a obține alte resurse deținute de alte procese și nici un proces nu poate avea resursele de care au nevoie, și în consecință părăsească. Această definiție este prea generală, deci este dificil de înțeles, pentru o mai bună înțelegere a acesteia vom lua în considerare tipurile de încuietori reciproce pe exemple.

Blocarea reciprocă a ordinului de sincronizare


Luați în considerare următoarea sarcină: trebuie să scrieți o metodă care efectuează o tranzacție care transferă o anumită sumă de bani dintr-un cont în altul. Soluția poate avea următoarea formă:


La prima vedere, acest cod este sincronizat destul de normal, avem o operație atomică pentru verificarea și modificarea stării contului sursă și schimbarea contului de primire. Dar, cu această strategie de sincronizare, poate exista o situație de blocare reciprocă. Să ne uităm la un exemplu despre cum se întâmplă acest lucru. Este necesar să se efectueze două tranzacții: de la contul A la contul B transfer x banii și de la contul B la contul A - y. De multe ori, această situație nu provoacă un impas, cu toate acestea, în circumstanțe nefericite, tranzacția 1 ține cont de monitor A, monitorul tranzacție 2 ia în considerare B. Rezultat - impas: 1 tranzacție așteaptă eliberarea de tranzacție 2 conturi Monitor B, dar pentru această tranzacție 2 trebuie să acceseze monitorul A, ocupat de tranzacție 1.
Una dintre marile probleme cu încuietori reciproce este că ele nu sunt ușor de găsit în timpul testelor. Chiar și în situația descrisă în exemplu, firele nu pot fi blocate, adică această situație nu va fi reprodusă în mod constant, ceea ce complică foarte mult diagnosticarea. În general, problema descrisă de non-determinism este tipică pentru multithreading (deși acest lucru nu este mai ușor). Prin urmare, în îmbunătățirea calității aplicațiilor multi-threaded, examinarea codului joacă un rol important, deoarece permite identificarea erorilor care sunt problematice de reproducere în timpul testelor. Acest lucru, desigur, nu înseamnă că aplicația nu trebuie să fie testată, doar despre revizuirea codului, de asemenea, nu uitați.
Ce trebuie să fac pentru a împiedica blocarea reciprocă a acestui cod? Această blocare este cauzată de faptul că sincronizarea contului poate avea loc într-o ordine diferită. Prin urmare, dacă vom introduce unele ordine în conturi (aceasta este o regulă care permite să spun că proiectul de lege este mai mică decât A prin B), atunci problema este rezolvată. Cum se face acest lucru? În primul rând, în cazul în care conturile au un fel de identificator unic (de exemplu, numărul de cont), numeric, o linie sau o altă cu conceptul natural al comenzii (linii pot fi comparate în ordine lexicografica. Putem presupune că am avut noroc și am mereu mai întâi putem ocupa monitorul unui cont mai mic și apoi mai mult (sau invers).

Cea de-a doua opțiune, dacă nu avem un astfel de identificator, va trebui să ne ridicăm noi înșine. Putem compara obiectele într-o primă aproximare prin codul hash. Cel mai probabil, acestea vor fi diferite. Dar dacă sunt toate la fel? Apoi trebuie să adăugați încă un obiect pentru sincronizare. Ar putea părea un pic sofisticat, dar ce puteți face? Și, în plus, al treilea obiect va fi folosit destul de rar. Rezultatul va arăta astfel:







Interconectarea între obiecte


Condițiile de blocare descrise reprezintă cel mai simplu caz de interblocare reciprocă. Adesea, în aplicațiile cu mai multe filetări, diverse obiecte încearcă să acceseze aceleași blocuri sincronizate. În acest caz, poate exista o interblocare reciprocă. Luați în considerare următorul exemplu: o cerere pentru managerul de zbor. Aeronavele sunt notificate operatorului atunci când ajung la destinație și solicită un permis de aterizare. Expeditorul păstrează toate informațiile despre avion care zboară în direcția sa și își pot construi poziția pe hartă.

Înțelegeți că există o eroare în acest cod, care poate duce la blocarea reciprocă mai dificilă decât în ​​cea precedentă. La prima vedere, nu are sincronizări repetate, dar nu este. Probabil ați observat deja că metodele setLocation ale clasei Plane și clasei getMap din clasa Dispatcher sunt sincronizate și invocă metodele sincronizate ale altor clase intern. Aceasta este în general o practică proastă. Modul în care poate fi corectat acest lucru va fi discutat în secțiunea următoare. În consecință, în cazul în care aeronava ajunge la locul respectiv, în același timp cu cineva decide să obțină o cartelă, poate apărea o blocare reciprocă. Adică, vor fi numite metode, getMap și setLocation, care vor fi ocupate de monitoarele dispeceratului și, respectiv, ale planului. Metoda getMap apelează atunci plan.getLocation (în special pentru instanța Planea care este ocupată în prezent), care va aștepta eliberarea monitorului pentru fiecare instanță Plane. În același timp, dispatcher.requestLanding va fi apelat în metoda setLocation, în timp ce monitorul instanței Dispatcher rămâne ocupat să deseneze harta. Rezultatul este o blocare reciprocă.

Deschideți apelurile


Pentru a evita situații precum cea descrisă în secțiunea anterioară, se recomandă utilizarea apelurilor deschise la metodele altor obiecte. Asta este, apelați metodele altor obiecte în afara blocului sincronizat. Dacă utilizați principiul apelurilor deschise pentru a rescrie metodele setLocation și getMap, capacitatea de interblocare va fi eliminată. Se va arăta, de exemplu, astfel:

Interblocarea resurselor


Încuietori mutuale pot apărea, de asemenea, atunci când încercați să accesați anumite resurse care pot utiliza doar un singur fir la un moment dat. Un exemplu este un grup de conexiuni de baze de date. Dacă unele fire au nevoie de acces la ambele conexiuni în același timp și primesc acest acces într-o ordine diferită, acest lucru poate duce la blocarea reciprocă. În mod fundamental, aceste tipuri de încuietori nu diferă de blocarea ordinului de sincronizare, cu excepția faptului că nu apar atunci când încercați să executați un anumit cod, dar când încercați să accesați resursele.

Cum să evitați blocarea?


Desigur, dacă codul este scris fără erori (exemple despre care am văzut în secțiunile anterioare), atunci nu vor exista blocări reciproce în el. Dar cine poate garanta că codul lui este scris fără erori? Desigur, testarea ajută la identificarea unei părți semnificative a erorilor, dar, după cum am văzut mai devreme, erorile din codul multietajat nu sunt ușor de diagnosticat și chiar și după testare nu puteți fi siguri că nu există situații de blocare reciprocă. Putem să protejăm cumva blocarea? Răspunsul este da. Tehnici similare sunt utilizate în motoarele de baze de date, care de multe ori trebuie restaurate după blocări reciproce (asociate cu mecanismul de tranzacționare din baza de date). Interfața Lock și implementările sale disponibile în pachetul java.util.concurrent.locks vă permit să încercați să ocupați monitorul asociat unei instanțe din această clasă cu metoda tryLock (returnează adevărat dacă monitorul poate fi ocupat). Să presupunem că avem o pereche de obiecte care implementează interfața Lock și trebuie să le luăm monitoarele pentru a evita blocarea reciprocă. Îl puteți implementa astfel:

După cum puteți vedea în acest program, ocupăm două monitoare, excluzând posibilitatea blocării reciproce. Notă bloc try- în cele din urmă este necesar din cauza java.util.concurrent.locks clase de pachete nu sunt exonera în mod automat monitorul, iar dacă în cursul îndeplinirii sarcinilor tale au existat unele excepții, monitorul se va închide într-o stare blocată.

Cum de a diagnostica interlock-urile reciproce?


JVM vă permite să diagnosticați încuietorile reciproce afișându-le în haldele de filete. Astfel de depozite includ informații despre starea curentului. Dacă este blocată, memoria conține informații despre monitorul, eliberarea căruia firul se așteaptă. Înainte de a scoate o coadă de fire, JVM scanează graficul monitorilor așteptați (ocupat) și dacă găsește cicluri - adaugă informații despre blocarea reciprocă, indicând monitoarele și firele participante.
O grămadă de fire cu o încuietoare reciprocă arată astfel:

Exemplu de mai sus arată explicit că două fire care lucrează cu baza de date s-au blocat reciproc.
Pentru a diagnostica interlocking-ul folosind această caracteristică JVM, trebuie să efectuați apeluri de operațiuni de tip dump în diferite locuri din program și să testați aplicația. Apoi, ar trebui să analizați jurnalele primite. În cazurile în care indică existența unei blocări reciproce, informațiile din dump vor ajuta la detectarea condițiilor de apariție a acesteia.

În general, situațiile descrise în exemplele de interblocare reciprocă nu ar trebui să fie permise. În astfel de cazuri, este posibil ca aplicația să funcționeze stabil. Dar nu uitați despre testarea și revizuirea codului. Acest lucru va ajuta la identificarea problemelor dacă apar. În cazurile în care dezvoltați un sistem pentru care recuperarea câmpului de interblocare este critică, puteți utiliza metoda descrisă în secțiunea "Cum se evită interblocarea?". În acest caz, metoda de blocare LockInterruptibly a interfeței Lock din pachetul java.util.concurrent.locks poate fi de asemenea utilă. Vă permite să întrerupeți firul care a ocupat monitorul utilizând această metodă (și astfel eliberați monitorul).







Articole similare

Trimiteți-le prietenilor: