Starvation i mobila applikationer — essens, orsaker och sätt att förebygga trådsvält

Författare: IT Sectr Publicerad: 2026-03-18 Lästid: 10 min

Starvation (trådsvält) — är situationen där en tråd inte får åtkomst till resursen som krävs för att fortsätta arbetet, trots att den själv är redo för exekvering. Enligt Baeldung (Java Thread Starvation, 2024) uppstår svält på grund av orättvis schemaläggning, när trådar med låg prioritet ständigt skjuts upp till förmån för trådar med högre prioritet. Till skillnad från Deadlock, Starvation blockerar inte tråden — den förblir i RUNNABLE-tillståndet, men får aldrig processtid.

Huvudpunkter

  • Starvation — situation där en tråd inte får åtkomst till en resurs, trots att den är redo för exekvering
  • Till skillnad från Deadlock, förblir tråden under svält i RUNNABLE-tillståndet — inte blockerad, men gör inga framsteg
  • Orättvis schemaläggning (t.ex. synkronisering via synchronized) — den främsta orsaken till Starvation på JVM
  • Fair Lock (ReentrantLock(true)) garanterar rättvis åtkomstordning till låset i köordning
  • Thread Priority inom mobilutveckling rekommenderas att inte ändras — Android Runtime hanterar prioriteterna själv

Vad är Starvation?

Starvation (trådsvält) — är ett problem inom flertrådad programmering där en tråd inte kan få åtkomst till resursen som krävs för att utföra en uppgift, trots att resursen inte är permanent blockerad av en annan tråd. Tråden befinner sig i RUNNABLE-tillståndet, men schemaläggaren eller synkroniseringsmekanismen skjuter systematiskt upp dess exekvering till förmån för andra trådar.

Inom mobilutveckling manifesterar sig Starvation som ojämn uppgiftsexekvering: vissa operationer utförs omedelbart, andra — med katastrofala förseningar. Till exempel kan en bakgrundstråd som synkroniserar data aldrig få åtkomst till databasen om UI-tråden och animeringshanterare ständigt ligger före. Enligt Android Developer Blog (Performance Matters, 2023) orsakas cirka 12% av missade bildrutor (jank) på Android av Starvation av bakgrundsuppgifter som renderingen är beroende av.

Den viktigaste skillnaden mellan Starvation och Deadlock — reversibilitet. Om systembelastningen minskar eller prioriteringarna omfördelas kan den svältande tråden få resursen och slutföra arbetet. Under ständigt hög belastning kan Starvation dock pågå under obestämd tid och skapa intrycket av en frusen applikation.

Orsaker till trådsvält

Orättvisa lås (Non-Fair Locks)

synchronized i Java och Kotlin — ett klassiskt exempel på en orättvis mekanism. Vid hög konkurrens kan JVM oändligt ge låset till samma aktiva trådar, medan andra trådar ständigt förlorar i racet. Detta är inte en bugg i JVM, utan en implementeringsegenskap: orättvisa lås ger högre genomströmning på bekostnad av enhetlig åtkomst. För mobila applikationer med 4-8 trådar är detta problem särskilt relevant.

Felaktig användning av prioriteter

Inställning av olika trådprioriteter kan leda till Starvation av trådar med låg prioritet. I Android Runtime fördelar Linux CFS-schemaläggaren (Completely Fair Scheduler) processtiden proportionellt mot prioriteterna, och om trådar med hög prioritet är ständigt aktiva kan trådar med låg prioritet aldrig få CPU. Google rekommenderar starkt att inte ändra trådprioriteter i Android — systemet hanterar dem själv.

Långa kritiska sektioner

Om en tråd håller låset för länge (utför tunga beräkningar, nätverksförfrågningar eller filoperationer inuti synchronized-blocket) svälter andra trådar som väntar på detta lås. Detta är särskilt farligt i Android, där långa operationer i UI-tråden orsakar ANR, och att flytta dem till bakgrundstrådar utan optimering av kritiska sektioner överför Starvation-problemet till arbetstrådar.

Exempel på Starvation i Kotlin-kod

Låt oss betrakta ett exempel där en tråd tar låset för ofta på grund av orättvis schemaläggning. Starvation demonstreras genom en oändlig loop av en tråd med hög prioritet, som inte tillåter en tråd med låg prioritet att få åtkomst till den delade resursen.

kotlin
class SharedResource {
    private val lock = Any()

    fun criticalSection(id: String) {
        synchronized(lock) {
            println("$id fick åtkomst")
            Thread.sleep(10)  // simulering av arbete
        }
    }
}

fun main() {
    val resource = SharedResource()

    // Tråd med hög prioritet — ständigt aktiv
    val highPriority = Thread {
        while (true) {
            resource.criticalSection("High")
        }
    }

    // Tråd med låg prioritet — kan aldrig få åtkomst
    val lowPriority = Thread {
        while (true) {
            resource.criticalSection("Low")
        }
    }

    highPriority.start()
    lowPriority.start()
    // "Low" kanske aldrig skriver ut meddelandet — Starvation!
}

I detta exempel tar highPriority-tråden ständigt låset och släpper det endast i 10 ms. På grund av den orättvisa naturen hos synchronized kommer JVM-schemaläggaren med hög sannolikhet att ge låset igen till samma tråd som precis släppte det — tråden med låg prioritet svälter. Lösningen är att använda ReentrantLock(true) med fair-flaggan, som garanterar ordningen i väntekön.

Den korrigerade versionen med fair lock säkerställer rättvis fördelning av åtkomst till resursen.

kotlin
class FairSharedResource {
    private val fairLock = ReentrantLock(true)  // fair = true

    fun criticalSection(id: String) {
        fairLock.lock()
        try {
            println("$id fick åtkomst (fair)")
            Thread.sleep(10)
        } finally {
            fairLock.unlock()
        }
    }
}

Starvation vs Deadlock vs Livelock

Tre klassiska problem inom flertrådad programmering — Starvation, Deadlock och Livelock — kombineras ofta, men deras mekanismer och sätt att eliminera skiljer sig åt. Starvation — tråden är redo men får inte resursen. Deadlock — trådar är blockerade av cyklisk väntan. Livelock — trådar är aktiva men gör inga framsteg.

ParameterStarvationDeadlockLivelock
TrådtillståndRUNNABLEBLOCKEDRUNNABLE
FramstegNejNejNej (även om aktiv)
CPU-förbrukningLågMinimalHög (upp till 100%)
OrsakOrättvis schemaläggningCyklisk väntanSamma reaktion på konflikt
HuvudlösningFair Lock, minska kritiska sektionerLåshierarkiÅterförsöksgräns, exponentiell backoff

Starvation anses mindre kritiskt än Deadlock, eftersom det inte är dödligt — när belastningen minskar kommer den svältande tråden så småningom att exekveras. Under verkliga användningsförhållanden för Android-applikationer, där minne och CPU är begränsade, kan Starvation dock pågå i minuter och skapa en oacceptabel användarupplevelse.

Hur man upptäcker Starvation

Thread Dump med upprepade avbildningar med korta intervall — grundmetoden för att upptäcka svält. Om en tråd konsekvent är i RUNNABLE-tillståndet, men dess anropsstack inte ändras under flera avbildningar — är detta ett klassiskt tecken på Starvation. I Android Studio används Android Profiler med inspelning av trådtillstånd över tid.

Automatiserad detektering är möjlig genom övervakning av exekveringstid för uppgifter. Om en uppgift med förutsägbar exekveringstid (t.ex. 50 ms) exekveras i 5 sekunder eller mer — finns det hög sannolikhet för Starvation. I mobila applikationer gör Firebase Performance Monitoring det möjligt att konfigurera anpassade spår (custom traces) för kritiska sektioner och få aviseringar när tröskelvärden överskrids.

För diagnos av Starvation orsakad av synchronized-block, använd Java Flight Recorder (JFR) (tillgänglig på Android via OpenJDK API) eller Async Profiler. Dessa verktyg visar vilka monitorer som har längst väntetid och vilka trådar som tävlar om varje monitor. JFR-data integreras med IntelliJ IDEA Ultimate via den inbyggda profilern.

Metoder för att förebygga trådsvält

Fair Lock (ReentrantLock med true-flagga)

ReentrantLock(true) garanterar att trådar får låset i köordning (FIFO). Till skillnad från synchronized tillåter inte fair lock att en tråd som precis har släppt låset omedelbart tar det igen. Detta eliminerar Starvation helt, även om det minskar den totala genomströmningen med 10-20% på grund av overhead för att upprätthålla kön.

Låsfria atomära strukturer

Lock-free datastrukturer (ConcurrentHashMap, AtomicReference, LongAdder) utesluter Starvation per definition, eftersom de inte innehåller lås som kan hållas av en enda tråd. Alla operationer använder processorns CAS-instruktioner, som garanterar framsteg för minst en tråd inom ett begränsat antal steg. För mobilutveckling, föredra ConcurrentLinkedQueue för uppgiftsköer.

Korta kritiska sektioner

Minimering av tiden som ett lås hålls — ett universellt sätt att minska risken för Starvation. Flytta tunga operationer (nätverk, disk I/O, komplexa beräkningar) utanför synchronized-blocket. Använd ReadWriteLock för scenarier där läsare inte borde svälta på grund av sällsynta skrivare. Kotlin Coroutines-biblioteket tillhandahåller Mutex med en suspending-mekanism som inte blockerar OS-tråden.

Villkorsvariabler och signaler

Condition.await() och signal() bör användas försiktigt: en tråd som väntar på Condition vaknar tillsammans med andra trådar (spurious wakeup) och alla tävlar om låset. Om en tråd efter await omedelbart återgår till väntan, medan andra lyckas ta låset — kan den svältande tråden vakna och somna oändligt. Kontrollera alltid villkoret i en while-loop, inte i en if, för att garantera omkontroll.

Vanliga frågor

Vad är skillnaden mellan Starvation och Priority Inversion?

Priority Inversion — är situationen där en tråd med låg prioritet håller ett lås som behövs av en tråd med hög prioritet. Som ett resultat väntar den högprioriterade tråden på den lågprioriterade tråden — prioriteterna vänds. Starvation är ett bredare problem: tråden får inte resursen oavsett prioritet, på grund av orättvis schemaläggning eller långa kritiska sektioner.

Kan Starvation uppstå i en enkeltrådad applikation?

Nej, Starvation — är ett problem inom flertrådad programmering. I enkeltrådad kod finns det ingen konkurrens om resurser och trådschemaläggning. Starvation kan dock uppstå i asynkron enkeltrådad kod (t.ex. JavaScript event loop) om en mikrouppgift oändligt skjuter upp exekveringen av andra via setTimeout med noll fördröjning.

Hur är Java Memory Model relaterad till Starvation?

JMM (Java Memory Model) definierar reglerna för synlighet av ändringar mellan trådar, men garanterar inte rättvis schemaläggning. synchronized i enlighet med JMM ger sekventiell konsistens — grundläggande korrekthet — men förhindrar inte Starvation. För rättvisa krävs ytterligare mekanismer som inte ingår i JMM-specifikationen.

Vad är Starvation i Android UI-tråden?

UI-tråden (Main Thread) kan inte svälta i klassisk mening, eftersom den har högst prioritet. Starvation uppstår dock när UI-tråden väntar på resultatet från en svältande bakgrundstråd. Typiskt scenario: AsyncTask eller coroutine laddar data, men kan inte få åtkomst till databasen på grund av konkurrens med andra trådar, och UI fryser i väntan.

Hur förebygger man Starvation i Kotlin Coroutines?

I coroutines, för att förebygga Starvation, använd limitedParallelism på Dispatchers.IO för att undvika att trådar tar slut. För synkronisering, använd Mutex från kotlinx.coroutines.sync — den pausar coroutinen, blockerar inte tråden, vilket minskar risken för svält. Undvik runBlocking i coroutines, eftersom det kan ta en pooltråd och orsaka Starvation för andra coroutines.

Sammanfattning

  • Starvation — situation där en tråd är redo för exekvering men inte får resursen på grund av orättvis schemaläggning
  • Till skillnad från Deadlock, är tråden vid svält i RUNNABLE-tillstånd och kan exekveras när belastningen minskar
  • Orättvisa lås (synchronized) och felaktig användning av prioriteter — de främsta orsakerna till Starvation
  • Fair Lock (ReentrantLock med true-flagga) garanterar FIFO-åtkomstordning och eliminerar svält helt
  • Lock-free strukturer (ConcurrentHashMap, AtomicReference) eliminerar Starvation på arkitekturnivå
  • Thread Dump med upprepade avbildningar och Java Flight Recorder — effektiva diagnosmetoder för Starvation
  • Korta kritiska sektioner och ReadWriteLock minskar sannolikheten för svält i system med hög belastning

Vi utvecklar en mobil applikation nyckelfärdigt

IT Sectr skapar iOS- och Android-applikationer för startups och företag sedan 2017. Vi ger dig råd och föreslår den bästa lösningen.

Diskutera projektet

Läs också