Livelock (actieve blokkering) — is een situatie in multithreaded programmeren waarbij threads niet geblokkeerd zijn, maar oneindig reageren op elkaars acties zonder nuttig werk te verrichten. Volgens Baeldung (Java Concurrency Guide, 2024), veranderen threads bij Livelock constant van toestand als reactie op de toestand van aangrenzende threads, maar geen enkele bereikt het doel. In tegenstelling tot Deadlock, verbruikt Livelock 100% CPU, wat de batterij van een mobiel apparaat snel leeg maakt.
Belangrijkste
Livelock (actieve blokkering) — is een situatie in een multithreaded systeem waarbij threads niet geblokkeerd zijn maar ook geen nuttig werk verrichten. Elke thread ontdekt dat hij niet verder kan werken en probeert dit te verhelpen, maar zijn acties veroorzaken dezelfde reactie bij andere threads. Als gevolg schakelt het systeem oneindig tussen toestanden zonder vooruitgang te boeken.
De klassieke analogie van Livelock — twee mensen ontmoeten elkaar in een smalle gang. Beiden proberen plaats te maken door opzij te stappen, maar beide doen tegelijkertijd dezelfde beweging en staan weer tegenover elkaar. Ze staan niet stil (dat zou Deadlock zijn), maar bewegen actief en kunnen toch niet langs elkaar. In programmering komt dit overeen met threads die constant bronnen vrijgeven en opnieuw innemen.
In mobiele ontwikkeling is Livelock bijzonder gevaarlijk omdat het onzichtbaar is voor de gebruiker: de applicatie loopt niet vast, de interface wordt niet geblokkeerd, maar de batterij raakt 2-3 keer sneller leeg door 100% CPU-belasting door achtergrondthreads. Volgens Google-tests (Android Battery Optimization, 2023) kan Livelock in een achtergrond-Service de batterijduur van het apparaat met 40% verkorten.
Livelock ontstaat wanneer meerdere threads dezelfde conflictreactiestrategie gebruiken. Als Thread A een bron niet kan vastleggen en zijn huidige bron vrijgeeft, en Thread B tegelijkertijd hetzelfde doet, herhalen beide de cyclus — en de situatie herhaalt zich oneindig. Dit is vooral kenmerkend voor algoritmen met TryLock en automatische vrijgave bij mislukking.
Wanneer threads een vaste vertraging gebruiken voor een herpoging, kunnen ze in een synchrone cyclus terechtkomen. Als beide threads even lang wachten, zullen ze tegelijkertijd opnieuw proberen de bron te bemachtigen en deze tegelijkertijd vrijgeven. Het probleem wordt opgelost met exponential backoff met een willekeurige component (jitter), zoals in het CSMA/CD-algoritme in Ethernet.
In mobiele ontwikkeling ontstaat Livelock vaak bij onjuiste implementatie van taakwachtrijen. Bijvoorbeeld wanneer een werkthread de verwerking van een bericht voltooit, maar door de prioriteringslogica constant de controle overdraagt aan een andere werkthread die hetzelfde doet. Dergelijke situaties zijn typisch voor aangepaste ThreadPoolExecutors met een niet-standaard RejectedExecutionHandler-beleid.
Laten we een situatie bekijken waarbij twee threads TryLock gebruiken en de bron vrijgeven bij mislukking. Actieve blokkering ontstaat omdat beide threads dezelfde logica toepassen en de pogingen synchroon herhalen.
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 — uitgevoerd!")
lock2.unlock()
lock1.unlock()
return
} else {
lock1.unlock() // vrijgeven en herhalen
}
}
Thread.sleep(50) // vaste vertraging — de sleutelfactor van Livelock
}
}
}
Als twee exemplaren van LivelockWorker worden gestart met een andere volgorde van lock1 en lock2, zullen ze in actieve blokkering terechtkomen. Elk zal de eerste bron vastleggen, de tweede niet krijgen, de eerste vrijgeven, 50 ms wachten en herhalen — oneindig, CPU verbruikend. Oplossing — een willekeurige component toevoegen aan de vertraging (jitter) en het aantal herpogingen beperken.
De gecorrigeerde versie gebruikt exponential backoff met willekeurige jitter. Na elke mislukte poging neemt de wachttijd toe met toevoeging van een willekeurige factor, wat de synchronisatie tussen threads verbreekt.
fun executeWithBackoff() {
var delay = 10L
var attempts = 0
while (attempts < 5) {
if (lock1.tryLock(delay, TimeUnit.MILLISECONDS)) {
if (lock2.tryLock(delay, TimeUnit.MILLISECONDS)) {
println("Succes!")
lock2.unlock(); lock1.unlock()
return
}
lock1.unlock()
}
delay = (delay * 2 + (0..50).random())
attempts++
}
println("Niet gelukt na 5 pogingen")
}
Ondanks de uiterlijke gelijkenis hebben Livelock en Deadlock fundamenteel verschillende mechanismen en gevolgen. Bij Deadlock zijn threads geblokkeerd en verbruiken ze geen CPU — de applicatie loopt gewoon vast. Bij Livelock zijn threads actief, verbruiken ze 100% CPU, maar verrichten ze geen nuttig werk. De keuze van de eliminatiestrategie hangt af van de juiste bepaling van het type blokkering.
| Parameter | Deadlock | Livelock |
|---|---|---|
| Threadstatus | BLOCKED / WAITING | RUNNABLE |
| CPU-verbruik | Minimaal | Hoog (90-100%) |
| Batterijverbruik | Laag | Hoog |
| Detectie | Thread Dump | CPU Profiler + visuele analyse |
| Typische oorzaak | Verschillende volgorde van lock-acquisitie | Dezelfde conflictreactiestrategie |
| Oplossing | Lock-hiërarchie | Retry limit + exponential backoff |
In mobiele ontwikkeling is het praktische verschil enorm. Deadlock leidt tot ANR en herstart van de applicatie — het wordt gedetecteerd en gerapporteerd via Google Play Console. Livelock blijft onopgemerkt: de applicatie lijkt te werken, maar de batterij is binnen een uur leeg en de gebruiker verwijdert gewoon de applicatie. Volgens Firebase Analytics (App Retention Report, 2024) verwijdert 68% van de gebruikers de applicatie als deze overmatig batterij verbruikt op de achtergrond.
Detectie van Livelock is moeilijker dan Deadlock omdat het systeem geen duidelijke signalen afgeeft — er zijn geen uitzonderingen, geen ANR, geen foutmeldingen. De belangrijkste diagnosemethode — CPU Profiler in Android Studio. Als een thread constant in de RUNNABLE-status is maar geen nuttige invoer-uitvoerbewerkingen of berekeningen uitvoert — is dit een vermoeden van Livelock.
Een bijkomend teken — abnormaal batterijverbruik bij inactiviteit van de applicatie. Android Battery Historian (een tool uit de Android SDK) bouwt grafieken van energieverbruik per component. Als de CPU Wakelock zonder zichtbare reden wordt vastgehouden — moet u Method Tracing uitvoeren en de callstack van verdachte threads analyseren.
Op codeniveau helpt loggen van herpogingen met threadId en tijd. Als de log duizenden herpogingen per seconde laat zien zonder enig succes — is dit Livelock. Het wordt aanbevolen om een Hystrix-achtige circuit breaker of een retry-teller met een drempelwaarde te implementeren die bij overschrijding de bewerking uitschakelt en de ontwikkelaar op de hoogte stelt via Crashlytics.
De eenvoudigste en meest betrouwbare manier — het aantal pogingen beperken om een bron vast te leggen. Als de bewerking na N pogingen niet is gelukt, gaat de thread naar de foutstatus en stelt de gebruiker op de hoogte. N wordt empirisch gekozen: voor mobiele applicaties meestal 3-5 pogingen. Dit elimineert oneindige Livelock volledig, ten koste van zeldzame valse alarmen bij hoge belasting.
In plaats van een vaste vertraging tussen pogingen wordt een exponentieel toenemende pauze met een willekeurige component gebruikt. Formule: delay = min(baseDelay * 2^attempt, maxDelay) + random(0, jitter). Deze benadering verbreekt niet alleen de synchronisatie van threads, maar vermindert ook de totale systeembelasting bij hoge concurrentie. Het wordt gebruikt in algoritmen van netwerkprotocollen en aanbevolen door Google voor de herpogingslogica van Firebase Realtime Database.
Het toewijzen van verschillende strategieën aan verschillende threads elimineert de oorzaak van Livelock — dezelfde reactie op conflict. Bijvoorbeeld, een thread met hoge prioriteit krijgt de bron zonder vrijgave, en een thread met lage prioriteit geeft vrij en wacht. In mobiele ontwikkeling kan de UI-thread prioriteit hebben bij het vastleggen van locks, en achtergrondwerkthreads kunnen TryLock met timeout gebruiken.
In sommige architecturen wordt Livelock op ontwerpniveau voorkomen: bronnen slechts in één richting vrijgeven. Bijvoorbeeld, als Thread A altijd de controle overdraagt aan Thread B via een vast kanaal (Channel), en B nooit probeert de controle terug te geven aan A — is een reactiecyclus onmogelijk. Pipeline-architectuur met eenrichtingsverwerkingsstadia in Android CameraX en MediaPipe elimineert Livelock tussen aangrenzende stadia volledig.
Veelgestelde vragen
Een oneindige lus is niet afhankelijk van externe factoren en herhaalt een bewerking zonder interactie met andere threads. Livelock is altijd een reactie op acties van andere threads: de thread verandert zijn gedrag als reactie op de toestand van aangrenzende threads, waardoor een gesloten terugkoppeling ontstaat. Thread Dump bij Livelock toont constante contextwisselingen.
In databases ontstaat Livelock wanneer een transactie constant wordt uitgesteld vanwege blokkeringen door andere transacties. Bijvoorbeeld, DBMS gebruikt het wait-die algoritme: als een transactie met een kortere starttijd conflicteert met een nieuwere, wordt deze teruggedraaid en opnieuw gestart, maar komt elke keer in hetzelfde conflict terecht. Dit wordt opgelost met randomized restart delay.
In sommige systemen heeft Livelock de voorkeur boven Deadlock, omdat threads actief blijven en het probleem kunnen detecteren. Bijvoorbeeld, in algoritmen voor optimistische vergrendeling (optimistic locking) is livelock-achtig gedrag toegestaan als retry limit een definitieve voltooiing garandeert. Dit is een compromis tussen prestaties en voortgangsgarantie.
Livelock is uiterst moeilijk te reproduceren in tests, omdat het een exacte overeenkomst van thread-timings vereist. Unittests worden deterministisch uitgevoerd en detecteren zelden actieve blokkering. Het wordt aanbevolen om Stress Testing te gebruiken met meerdere uitvoeringen onder belasting en monitoring van CPU-verbruik in de profiler.
Op de server leidt Livelock tot prestatievermindering en time-outs, maar de server schaalt horizontaal. Op Android ontlaadt Livelock de batterij en oververhit het apparaat, waardoor een slechtere gebruikerservaring ontstaat. Bovendien is het aantal CPU-kernen op mobiele apparaten beperkt, waardoor Livelock sneller leidt tot het onbruikbaar worden van het hele systeem.
Samenvatting
We ontwikkelen een mobiele applicatie turnkey
IT Sectr creëert sinds 2017 iOS- en Android-applicaties voor startups en bedrijven. We adviseren u en stellen de beste oplossing voor.
Lees ook