Livelock (aktív blokkolás) — olyan helyzet a többszálú programozásban, amikor a szálak nincsenek blokkolva, de végtelenül reagálnak egymás akcióira anélkül, hogy hasznos munkát végeznének. A Baeldung (Java Concurrency Guide, 2024) szerint Livelock esetén a szálak folyamatosan változtatják az állapotukat a szomszédos szálak állapotára válaszul, de egyik sem éri el a célt. A Deadlock-tól eltérően a Livelock 100% CPU-t fogyaszt, ami gyorsan lemeríti a mobileszköz akkumulátorát.
Főbb pontok
Livelock (aktív blokkolás) — olyan helyzet egy többszálú rendszerben, amikor a szálak nincsenek blokkolva, de nem végeznek hasznos munkát sem. Minden szál észleli, hogy nem tudja folytatni a munkát, és megpróbálja orvosolni, de az ő akciói ugyanazt a reakciót váltják ki más szálakban. Ennek eredményeként a rendszer végtelenül váltogatja az állapotokat anélkül, hogy előrehaladna.
A Livelock klasszikus analógiája — két ember találkozik egy szűk folyosón. Mindegyik megpróbál utat engedni félrelépve, de mindketten egyszerre ugyanazt a mozdulatot teszik, és újra egymással szemben állnak. Ők nem állnak egy helyben (az Deadlock lenne), hanem aktívan mozognak, de mégsem tudnak elhaladni egymás mellett. A programozásban ez azoknak a szálaknak felel meg, amelyek folyamatosan felszabadítják és újra lefoglalják az erőforrásokat.
A mobilfejlesztésben a Livelock különösen veszélyes, mert láthatatlan a felhasználó számára: az alkalmazás nem fagy le, a felület nem blokkolódik, de az akkumulátor 2-3-szor gyorsabban lemerül a háttérszálak 100% CPU-terhelése miatt. A Google tesztek szerint (Android Battery Optimization, 2023) a Livelock egy háttérszolgáltatásban 40%-kal csökkentheti az eszköz akkumulátoros üzemidejét.
A Livelock akkor keletkezik, amikor több szál ugyanazt a konfliktusreakció-stratégiát használja. Ha az A szál nem tud erőforrást foglalni és felszabadítja a jelenlegi erőforrását, a B szál pedig ugyanezt teszi egyidejűleg, mindketten ismétlik a ciklust — és a helyzet végtelenül ismétlődik. Ez különösen jellemző a TryLock és automatikus felszabadítás algoritmusaira.
Amikor a szálak fix késleltetést használnak az újrapróbálkozás előtt, szinkron ciklusba kerülhetnek. Ha mindkét szál ugyanannyit vár, ismét egyidejűleg próbálják megfoglalni az erőforrást, és egyidejűleg szabadítják fel. A probléma exponential backoff használatával oldható meg véletlenszerű összetevővel (jitter), mint az Ethernet CSMA/CD algoritmusában.
A mobilfejlesztésben a Livelock gyakran a feladatsorok helytelen implementációja miatt keletkezik. Például amikor egy munkaszál befejezi egy üzenet feldolgozását, de a prioritási logika miatt folyamatosan átadja a vezérlést egy másik munkaszálnak, amely ugyanezt teszi. Az ilyen helyzetek jellemzőek a nem szabványos RejectedExecutionHandler politikával rendelkező egyedi ThreadPoolExecutor-okra.
Vizsgáljuk meg azt a helyzetet, amikor két szál TryLock-ot használ, és hiba esetén felszabadítja az erőforrást. Aktív blokkolás azért keletkezik, mert mindkét szál ugyanazt a logikát alkalmazza, és szinkronban ismétli a próbálkozásokat.
import java.util.concurrent.locks.ReentrantLock
import java.util.concurrent.TimeUnit
class LivelockWorker(private val name: String,
private val lock1: ReentrantLock,
private val lock2: ReentrantLock) {
fun execute() {
while (true) {
if (lock1.tryLock(50, TimeUnit.MILLISECONDS)) {
if (lock2.tryLock(50, TimeUnit.MILLISECONDS)) {
println("$name — végrehajtva!")
lock2.unlock()
lock1.unlock()
return
} else {
lock1.unlock() // felszabadítunk és ismétlünk
}
}
Thread.sleep(50) // fix késleltetés — a Livelock kulcstényezője
}
}
}
Ha két LivelockWorker példányt a lock1 és lock2 eltérő sorrendben történő foglalásával indítunk, aktív blokkolásba kerülnek. Mindegyik lefoglalja az első erőforrást, nem kapja meg a másodikat, felszabadítja az elsőt, vár 50 ms-t és ismétli — végtelenül, CPU-t fogyasztva. Javítás — véletlenszerű összetevő hozzáadása a késleltetéshez (jitter) és az újrapróbálkozások számának korlátozása.
A javított verzió exponential backoff-ot használ véletlenszerű jitter-rel. Minden sikertelen próbálkozás után a várakozási idő növekszik egy véletlenszerű szorzó hozzáadásával, ami megtöri a szálak közötti szinkronizációt.
fun executeWithBackoff() {
var delay = 10L
var attempts = 0
while (attempts < 5) {
if (lock1.tryLock(delay, TimeUnit.MILLISECONDS)) {
if (lock2.tryLock(delay, TimeUnit.MILLISECONDS)) {
println("Siker!")
lock2.unlock(); lock1.unlock()
return
}
lock1.unlock()
}
delay = (delay * 2 + (0..50).random())
attempts++
}
println("Nem sikerült 5 próbálkozás után")
}
A külső hasonlóság ellenére a Livelock és Deadlock alapvetően különböző mechanizmusokkal és következményekkel rendelkezik. Deadlock esetén a szálak blokkolva vannak, és nem fogyasztanak CPU-t — az alkalmazás egyszerűen lefagy. Livelock esetén a szálak aktívak, 100% CPU-t fogyasztanak, de nem végeznek hasznos munkát. A megszüntetési stratégia kiválasztása a blokkolás típusának helyes meghatározásától függ.
| Paraméter | Deadlock | Livelock |
|---|---|---|
| Szálak állapota | BLOCKED / WAITING | RUNNABLE |
| CPU fogyasztás | Minimális | Magas (90-100%) |
| Akku fogyasztás | Alacsony | Magas |
| Észlelés | Thread Dump | CPU Profiler + vizuális elemzés |
| Tipikus ok | Különböző zárolási sorrend | Azonos konfliktusreakció-stratégia |
| Javítás | Zárolási hierarchia | Retry limit + exponential backoff |
A mobilfejlesztésben a gyakorlati különbség óriási. A Deadlock ANR-hez és az alkalmazás újraindításához vezet — észlelésre kerül és jelentésre a Google Play Console-on keresztül. A Livelock észrevétlen marad: az alkalmazás működőképesnek tűnik, de az akkumulátor egy óra alatt lemerül, és a felhasználó egyszerűen törli az alkalmazást. A Firebase Analytics szerint (App Retention Report, 2024) a felhasználók 68%-a törli az alkalmazást, ha az túlzottan fogyasztja az akkumulátort a háttérben.
A Livelock észlelése nehezebb, mint a Deadlock-é, mert a rendszer nem ad egyértelmű jeleket — nincsenek kivételek, nincs ANR, nincsenek hibaüzenetek. A fő diagnosztikai módszer — CPU Profiler az Android Studio-ban. Ha egy szál folyamatosan RUNNABLE állapotban van, de nem végez hasznos bemeneti-kimeneti műveleteket vagy számításokat — ez Livelock gyanúja.
További jel — rendellenes akkumulátorfogyasztás az alkalmazás tétlensége esetén. Az Android Battery Historian (egy eszköz az Android SDK-ból) energiafogyasztási grafikonokat készít komponensenként. Ha a CPU Wakelock látható ok nélkül fennmarad — érdemes Method Tracing-et futtatni és elemezni a gyanús szálak hívási vermét.
Kód szintjén segít az újrapróbálkozások naplózása threadId és idő megadásával. Ha a napló másodpercenként több ezer újrapróbálkozást mutat egyetlen siker nélkül — ez Livelock. Javasolt egy Hystrix-szerű circuit breaker vagy egy retry számláló bevezetése egy küszöbértékkel, amely túllépés esetén kikapcsolja a műveletet és értesíti a fejlesztőt a Crashlytics-en keresztül.
A legegyszerűbb és legmegbízhatóbb módszer — korlátozni a próbálkozások számát az erőforrás megszerzésére. Ha N próbálkozás után a művelet nem sikerült, a szál hibaállapotba kerül, és értesíti a felhasználót. N-t empirikusan választják: mobilalkalmazások esetén általában 3-5 próbálkozás. Ez teljesen kiküszöböli a végtelen Livelock-ot a ritka téves riasztások árán magas terhelés esetén.
A próbálkozások közötti fix késleltetés helyett exponenciálisan növekvő szünet használatos véletlenszerű összetevővel. Képlet: delay = min(baseDelay * 2^attempt, maxDelay) + random(0, jitter). Ez a megközelítés nemcsak megtöri a szálak szinkronizációját, hanem csökkenti a rendszer teljes terhelését magas konkurencia esetén. Hálózati protokollok algoritmusaiban használatos, és a Google ajánlja a Firebase Realtime Database újrapróbálkozási logikájához.
Különböző stratégiák hozzárendelése a különböző szálakhoz megszünteti a Livelock okát — az azonos reakciót a konfliktusra. Például a magas prioritású szál erőforrást kap felszabadítás nélkül, az alacsony prioritású pedig felszabadít és vár. A mobilfejlesztésben az UI szál elsőbbséget kaphat a zárolások megszerzésében, a háttér munkaszálak pedig TryLock-ot használhatnak időtúllépéssel.
Egyes architektúrákban a Livelock tervezési szinten megelőzhető: erőforrások felszabadítása csak egy irányban. Például, ha az A szál mindig átadja a vezérlést a B szálnak egy fix csatornán (Channel) keresztül, és B soha nem próbálja visszaadni a vezérlést A-nak — a reakcióciklus lehetetlen. Az egyirányú feldolgozási szakaszokkal rendelkező pipeline architektúra az Android CameraX-ben és a MediaPipe-ban teljesen kiküszöböli a Livelock-ot a szomszédos szakaszok között.
Gyakran Ismételt Kérdések
A végtelen ciklus nem függ külső tényezőktől, és egy műveletet ismétel más szálakkal való interakció nélkül. A Livelock mindig reakció más szálak akcióira: a szál megváltoztatja viselkedését a szomszédos szálak állapotára válaszul, zárt visszacsatolást hozva létre. A Thread Dump Livelock esetén állandó kontextusváltást mutat.
Az adatbázisokban a Livelock akkor keletkezik, amikor egy tranzakció folyamatosan elhalasztódik más tranzakciók blokkolásai miatt. Például a DBMS a wait-die algoritmust használja: ha egy rövidebb kezdési idejű tranzakció ütközik egy újabbal, visszavonásra és újraindításra kerül, de minden alkalommal ugyanabba az ütközésbe fut. Ez randomized restart delay segítségével oldható meg.
Egyes rendszerekben a Livelock előnyösebb a Deadlock-nál, mert a szálak aktívak maradnak és észlelhetik a problémát. Például az optimista zárolás (optimistic locking) algoritmusaiban a livelock-szerű viselkedés megengedett, ha a retry limit garantálja a végső befejezést. Ez kompromisszum a teljesítmény és a haladás garanciája között.
A Livelock rendkívül nehéz reprodukálni tesztekben, mert a szálak időzítésének pontos egybeesését igényli. Az egységtesztek determinisztikusan futnak, és ritkán észlelik az aktív blokkolást. Javasolt a Stress Testing használata többszöri futtatással terhelés alatt, és a CPU-fogyasztás monitorozása a profilozóban.
A szerveren a Livelock teljesítményromláshoz és időtúllépésekhez vezet, de a szerver vízszintesen skálázható. Androidon a Livelock lemeríti az akkumulátort és túlmelegíti az eszközt, rosszabb felhasználói élményt teremtve. Ezenkívül a mobileszközökön korlátozott a CPU-magok száma, így a Livelock gyorsabban vezet a teljes rendszer működésképtelenségéhez.
Összegzés
Kulcsrakész mobilalkalmazást fejlesztünk
Az IT Sectr 2017 óta készít iOS és Android alkalmazásokat induló vállalkozásoknak és vállalkozásoknak. Tanácsot adunk, és a legjobb megoldást javasoljuk.
Olvassa el is