Zastoj u Java niti i Livelock

1. Pregled

Iako višenitnost pomaže u poboljšanju performansi aplikacije, ona također dolazi s određenim problemima. U ovom ćemo tutorialu uz pomoć Java primjera razmotriti dva takva problema, mrtvu točku i zaostatak.

2. Zastoj

2.1. Što je mrtva točka?

Zastoj se događa kada dvije ili više niti zauvijek čekaju bravu ili resurs koji drži druga nit. Slijedom toga, aplikacija se može zaustaviti ili propasti jer mrtve točke ne mogu napredovati.

Problem klasičnih filozofa blagovaonice lijepo pokazuje probleme sinkronizacije u okruženju s više niti i često se koristi kao primjer mrtve točke.

2.2. Primjer mrtve točke

Prvo, pogledajmo jednostavan Java primjer za razumijevanje mrtve točke.

U ovom primjeru stvorit ćemo dvije niti, T1 i T2. Nit T1 poziva operacija1, i konac T2 poziva operacijama.

Za dovršetak njihovih operacija provucite konac T1 treba steći brava1 prvo pa onda brava2, dok nit T2 treba steći brava2 prvo pa onda brava1. Dakle, u osnovi obje niti pokušavaju steći brave u suprotnom redoslijedu.

Sada, napišimo ZastojPrimjer razred:

javna klasa DeadlockExample {private Lock lock1 = new ReentrantLock (true); private Lock lock2 = novi ReentrantLock (istina); javna statička void glavna (String [] args) {DeadlockExample deadlock = new DeadlockExample (); nova nit (zastoj :: operation1, "T1"). start (); nova nit (zastoj :: operation2, "T2"). start (); } javna void operacija1 () {lock1.lock (); ispis ("brava1 nabavljena, čeka se dobivanje brave2."); spavanje (50); lock2.lock (); ispis ("stečena brava2"); ispis ("izvršavanje prve operacije."); lock2.unlock (); lock1.unlock (); } javna void operacija2 () {lock2.lock (); ispis ("brava2 nabavljena, čeka se dobivanje brave1."); spavanje (50); lock1.lock (); ispis ("stečena brava1"); ispis ("izvršavanje druge operacije."); lock1.unlock (); lock2.unlock (); } // pomoćne metode}

Pokrenimo sada ovaj primjer mrtve točke i primijetimo izlaz:

Navoj T1: zaključana1 stečena, čeka se dobivanje blokade2. Navoj T2: zaključana2, nabavljena, čeka na dobivanje lock1.

Jednom kada pokrenemo program, možemo vidjeti da program rezultira zastojem i da nikada neće izaći. Zapisnik prikazuje tu nit T1 čeka brava2, koji se drži koncem T2. Slično tome, nit T2 čeka brava1, koji se drži koncem T1.

2.3. Izbjegavanje mrtve točke

Zastoj je čest problem istodobnosti u Javi. Stoga bismo trebali dizajnirati Java aplikaciju kako bismo izbjegli bilo kakve potencijalne mrtve točke.

Za početak bismo trebali izbjeći potrebu za stjecanjem više brava za konac. Međutim, ako nit treba više brava, trebali bismo osigurati da svaka nit stekne brave istim redoslijedom, izbjegavajte bilo kakvu cikličku ovisnost u akviziciji brave.

Možemo i koristiti pokušaji zaključavanja s vremenskim ograničenjem, poput tryLock metoda u Zaključaj sučelje, kako bi se osiguralo da se nit ne blokira beskonačno ako ne može dobiti bravu.

3. Livelock

3.1. Što je Livelock

Livelock je još jedan problem istodobnosti i sličan je mrtvoj točki. U livelocku, dvije ili više niti neprestano prenose stanja među sobom umjesto da čekamo beskrajno kao što smo vidjeli u primjeru mrtve točke. Slijedom toga, niti ne mogu izvršavati svoje zadatke.

Sjajan primjer livelock-a je sustav za razmjenu poruka gdje, kada se dogodi izuzetak, potrošač poruke vraća transakciju i vraća je natrag na čekanje. Tada se ista poruka više puta čita iz reda, samo da bi se izazvala nova iznimka i vratila u red. Potrošač nikada neće preuzeti bilo koju drugu poruku iz reda.

3.2. Primjer Livelock-a

Sada ćemo, kako bismo demonstrirali stanje livelocka, uzeti isti primjer mrtve točke o kojem smo ranije raspravljali. U ovom primjeru također nit T1 poziva operacija1 i konac T2 poziva operacija2. Međutim, malo ćemo promijeniti logiku ovih operacija.

Obje niti trebaju dvije brave da bi dovršile svoj posao. Svaka nit dobiva svoju prvu bravu, ali utvrđuje da druga bravica nije dostupna. Dakle, kako bi se omogućilo da druga nit završi prva, svaka nit otpušta svoju prvu bravu i pokušava ponovno dobiti obje brave.

Demonstrirajmo livelock s Primjer uživo razred:

javna klasa LivelockExample {private Lock lock1 = new ReentrantLock (true); private Lock lock2 = novi ReentrantLock (istina); public static void main (String [] args) {LivelockExample livelock = novi LivelockExample (); nova nit (livelock :: operation1, "T1"). start (); nova nit (livelock :: operation2, "T2"). start (); } javna void operacija1 () {while (true) {tryLock (lock1, 50); ispis ("lock1 je nabavljen, pokušava se dobiti lock2."); spavanje (50); if (tryLock (lock2)) {print ("lock2 stečen."); } else {print ("ne mogu dobiti lock2, otpuštajući lock1."); lock1.unlock (); nastaviti; } ispis ("izvršavanje prve operacije."); pauza; } lock2.unlock (); lock1.unlock (); } javna void operacija2 () {while (true) {tryLock (lock2, 50); ispis ("lock2 je nabavljen, pokušava se dobiti lock1."); spavanje (50); if (tryLock (lock1)) {print ("lock1 stečen."); } else {print ("ne mogu dobiti lock1, otpuštajući lock2."); lock2.unlock (); nastaviti; } ispis ("izvršavanje druge operacije."); pauza; } lock1.unlock (); lock2.unlock (); } // pomoćne metode}

Ajmo sada pokrenuti ovaj primjer:

Nit T1: zaključana1 stečena, pokušava se dobiti zaključana2. Nit T2: zaključana2 stečena, pokušava se dobiti zaključana1. Navoj T1: ne može dobiti lock2, otpuštajući lock1. Navoj T2: ne može dobiti blokadu1, otpuštajući blokadu2. Nit T2: zaključana2 stečena, pokušava se dobiti zaključana1. Nit T1: zaključana1 stečena, pokušava se dobiti zaključana2. Navoj T1: ne može dobiti lock2, otpuštajući lock1. Nit T1: zaključana1 stečena, pokušava se dobiti zaključana2. Navoj T2: ne može dobiti blokadu1, otpuštajući blokadu2. ..

Kao što možemo vidjeti u zapisnicima, obje niti više puta stječu i otpuštaju brave. Zbog toga niti jedna nit ne može dovršiti operaciju.

3.3. Izbjegavanje Livelock-a

Da bismo izbjegli livelock, moramo istražiti stanje koje uzrokuje livelock, a zatim u skladu s tim pronaći rješenje.

Na primjer, ako imamo dvije niti koje uzastopno stječu i oslobađaju brave, što rezultira livelockom, možemo dizajnirati kôd tako da niti pokušaju u slučajnim intervalima dobiti brave. To će nitima pružiti dobru priliku da nabave brave koje su im potrebne.

Drugi način da se riješi problem oživljavanja u primjeru sustava za razmjenu poruka o kojem smo ranije raspravljali je stavljanje neuspjelih poruka u zasebni red za daljnju obradu, umjesto da ih ponovno vratite u isti red.

4. Zaključak

U ovom uputstvu raspravljali smo o mrtvoj točki i zastoju. Također, pregledali smo primjere Java kako bismo demonstrirali svaki od ovih problema i ukratko se dotaknuli kako ih možemo izbjeći.

Kao i uvijek, cjeloviti kod korišten u ovom primjeru može se naći na GitHubu.