Starvation în aplicațiile mobile — esență, cauze și metode de prevenire a înfometării thread-ului

Autor: IT Sectr Publicat: 2026-03-18 Timp de citire: 10 min

Starvation (înfometarea thread-ului) — este situația în care un thread nu obține acces la resursa necesară pentru continuarea lucrului, deși este pregătit pentru execuție. Conform Baeldung (Java Thread Starvation, 2024), înfometarea apare din cauza planificării inechitabile, când thread-urile cu prioritate scăzută sunt amânate constant în favoarea celor cu prioritate mai mare. Spre deosebire de Deadlock, Starvation nu blochează thread-ul — acesta rămâne în starea RUNNABLE, dar nu primește niciodată timp de procesor.

Principalele puncte

  • Starvation — situația în care un thread nu obține acces la resursă, deși este pregătit pentru execuție
  • Spre deosebire de Deadlock, thread-ul în timpul înfometării rămâne în starea RUNNABLE — nu este blocat, dar nu progresează
  • Planificarea inechitabilă (de exemplu, sincronizarea prin synchronized) — cauza principală a Starvation pe JVM
  • Fair Lock (ReentrantLock(true)) garantează ordinea echitabilă de acces la blocare în ordinea cozii
  • Thread Priority în dezvoltarea mobilă se recomandă să nu fie modificată — Android Runtime gestionează singur prioritățile

Ce este Starvation?

Starvation (înfometarea thread-ului) — este o problemă a programării multi-thread în care un thread nu poate obține acces la resursa necesară pentru îndeplinirea sarcinii, deși resursa nu este blocată pentru totdeauna de un alt thread. Thread-ul se află în starea RUNNABLE, dar planificatorul sau mecanismul de sincronizare amână sistematic execuția lui în favoarea altor thread-uri.

În dezvoltarea mobilă, Starvation se manifestă ca execuție inegală a sarcinilor: unele operații se execută instantaneu, altele — cu întârzieri catastrofale. De exemplu, thread-ul de fundal care sincronizează datele poate să nu obțină niciodată acces la baza de date dacă thread-ul UI și handler-ele de animație îl depășesc constant. Potrivit Android Developer Blog (Performance Matters, 2023), aproximativ 12% din cazurile de cadre pierdute (jank) pe Android sunt cauzate de Starvation a sarcinilor de fundal de care depinde randarea.

Diferența cheie dintre Starvation și Deadlock — reversibilitatea. Dacă sarcina sistemului scade sau prioritățile sunt redistribuite, thread-ul înfometat poate obține resursa și finaliza lucrul. Cu toate acestea, în condiții de sarcină ridicată constantă, Starvation poate dura nelimitat, creând impresia unei aplicații înghețate.

Cauzele înfometării thread-ului

Blocări inechitabile (Non-Fair Locks)

synchronized în Java și Kotlin — exemplul clasic de mecanism inechitabil. La concurență ridicată, JVM poate acorda blocarea la nesfârșit acelorași thread-uri active, în timp ce alte thread-uri pierd constant în cursă. Aceasta nu este o eroare a JVM, ci o caracteristică a implementării: blocările inechitabile asigură un randament mai mare în detrimentul uniformității accesului. Pentru aplicațiile mobile cu 4-8 thread-uri, această problemă este deosebit de relevantă.

Utilizarea incorectă a priorităților

Setarea priorităților diferite ale thread-urilor poate duce la Starvation a thread-urilor cu prioritate scăzută. În Android Runtime, planificatorul CFS (Completely Fair Scheduler) al Linux-ului distribuie timpul procesorului proporțional cu prioritățile, iar dacă thread-urile cu prioritate ridicată sunt constant active, cele cu prioritate scăzută pot să nu primească niciodată CPU. Google recomandă categoric să nu modificați prioritățile thread-urilor în Android — sistemul le gestionează singur.

Secțiuni critice lungi

Dacă un thread menține blocarea prea mult timp (execută calcule grele, cereri de rețea sau operații cu fișiere în interiorul blocului synchronized), alte thread-uri care așteaptă această blocare înfometează. Acest lucru este deosebit de periculos în Android, unde operațiile lungi în thread-ul UI cauzează ANR, iar transferul lor în thread-uri de fundal fără optimizarea secțiunilor critice transferă problema Starvation către thread-urile de lucru.

Exemplu de Starvation în cod Kotlin

Să considerăm un exemplu în care un thread preia blocarea prea des din cauza planificării inechitabile. Starvation este demonstrată printr-o buclă infinită a thread-ului cu prioritate ridicată, care nu permite thread-ului cu prioritate scăzută să obțină acces la resursa comună.

kotlin
class SharedResource {
    private val lock = Any()

    fun criticalSection(id: String) {
        synchronized(lock) {
            println("$id a obținut acces")
            Thread.sleep(10)  // simularea lucrului
        }
    }
}

fun main() {
    val resource = SharedResource()

    // Thread cu prioritate ridicată — activ constant
    val highPriority = Thread {
        while (true) {
            resource.criticalSection("High")
        }
    }

    // Thread cu prioritate scăzută — poate să nu obțină niciodată acces
    val lowPriority = Thread {
        while (true) {
            resource.criticalSection("Low")
        }
    }

    highPriority.start()
    lowPriority.start()
    // „Low" poate să nu afișeze niciodată mesajul — Starvation!
}

În acest exemplu, thread-ul highPriority preia constant blocarea și o eliberează doar pentru 10 ms. Din cauza naturii inechitabile a lui synchronized, planificatorul JVM cu o probabilitate mare va acorda blocarea din nou aceluiași thread care tocmai a eliberat-o — thread-ul cu prioritate scăzută înfometează. Soluția — utilizarea ReentrantLock(true) cu indicatorul fair, care garantează ordinea în coada de așteptare.

Versiunea corectată cu fair lock asigură distribuirea echitabilă a accesului la resursă.

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

    fun criticalSection(id: String) {
        fairLock.lock()
        try {
            println("$id a obținut acces (fair)")
            Thread.sleep(10)
        } finally {
            fairLock.unlock()
        }
    }
}

Starvation vs Deadlock vs Livelock

Trei probleme clasice ale multi-threading-ului — Starvation, Deadlock și Livelock — sunt adesea combinate, dar mecanismele lor și modalitățile de eliminare sunt diferite. Starvation — thread-ul este pregătit, dar nu primește resursa. Deadlock — thread-urile sunt blocate de așteptarea ciclică. Livelock — thread-urile sunt active, dar nu progresează.

ParametruStarvationDeadlockLivelock
Starea thread-uluiRUNNABLEBLOCKEDRUNNABLE
ProgresNuNuNu (deși activ)
Consum CPUScăzutMinimRidicat (până la 100%)
CauzăPlanificare inechitabilăAșteptare ciclicăAceeași reacție la conflict
Soluția principalăFair Lock, reducerea secțiunilor criticeIerarhia blocărilorLimită reîncercări, backoff exponențial

Starvation este considerată mai puțin critică decât Deadlock, deoarece nu este fatală — la scăderea sarcinii, thread-ul înfometat se va executa în cele din urmă. Cu toate acestea, în condițiile de utilizare reală a aplicațiilor Android, unde memoria și CPU sunt limitate, Starvation poate dura minute întregi, creând o experiență de utilizare inacceptabilă.

Cum să depistăm Starvation

Thread Dump cu prelevări repetate la intervale scurte — metoda de bază de detectare a înfometării. Dacă un thread se află constant în starea RUNNABLE, dar stiva sa de apeluri nu se modifică pe parcursul mai multor dump-uri — acesta este un semn clasic de Starvation. În Android Studio, pentru aceasta se utilizează Android Profiler cu înregistrarea stării thread-urilor în timp.

Detectarea automatizată este posibilă prin monitorizarea timpului de execuție a sarcinilor. Dacă o sarcină cu timp de execuție previzibil (de exemplu, 50 ms) se execută timp de 5 secunde sau mai mult — există o probabilitate ridicată de Starvation. În aplicațiile mobile, Firebase Performance Monitoring permite configurarea de trace-uri personalizate pentru secțiuni critice și primirea de notificări la depășirea valorilor prag.

Pentru diagnosticarea Starvation cauzată de blocurile synchronized, utilizați Java Flight Recorder (JFR) (disponibil pe Android prin OpenJDK API) sau Async Profiler. Aceste instrumente arată care monitoare au cel mai mare timp de așteptare și care thread-uri concurează pentru fiecare monitor. Datele JFR se integrează cu IntelliJ IDEA Ultimate prin profilatorul încorporat.

Metode de prevenire a înfometării thread-ului

Fair Lock (ReentrantLock cu indicatorul true)

ReentrantLock(true) garantează că thread-urile primesc blocarea în ordinea cozii (FIFO). Spre deosebire de synchronized, fair lock nu permite situația în care thread-ul care tocmai a eliberat blocarea o preia imediat din nou. Aceasta elimină complet Starvation, deși reduce randamentul general cu 10-20% din cauza cheltuielilor suplimentare pentru menținerea cozii.

Structuri atomice fără blocări

Structurile de date Lock-free (ConcurrentHashMap, AtomicReference, LongAdder) elimină Starvation prin definiție, deoarece nu conțin blocări care pot fi menținute de un singur thread. Toate operațiile utilizează instrucțiunile CAS ale procesorului, care garantează progresul a cel puțin unui thread într-un număr finit de pași. Pentru dezvoltarea mobilă, preferați ConcurrentLinkedQueue pentru cozile de sarcini.

Secțiuni critice scurte

Minimizarea timpului de menținere a blocării — o modalitate universală de a reduce riscul de Starvation. Scoateți operațiile grele (rețea, intrare-ieșire pe disc, calcule complexe) în afara blocului synchronized. Utilizați ReadWriteLock pentru scenarii în care cititorii nu ar trebui să înfometeze din cauza scriitorilor rari. Biblioteca Kotlin Coroutines furnizează Mutex cu un mecanism de suspendare care nu blochează thread-ul sistemului de operare.

Variabile de condiție și semnale

Condition.await() și signal() trebuie utilizate cu prudență: thread-ul care așteaptă pe Condition se trezește împreună cu alte thread-uri (spurious wakeup) și toate concurează pentru blocare. Dacă un thread după await revine imediat la așteptare, iar altele reușesc să preia blocarea — thread-ul înfometat se poate trezi și adormi la nesfârșit. Verificați întotdeauna condiția într-o buclă while, nu în if, pentru a garanta reverificarea.

Întrebări frecvente

Care este diferența dintre Starvation și inversarea priorității (Priority Inversion)?

Priority Inversion — este situația în care un thread cu prioritate scăzută menține o blocare necesară unui thread cu prioritate ridicată. Ca rezultat, thread-ul cu prioritate ridicată îl așteaptă pe cel cu prioritate scăzută — prioritățile se inversează. Starvation este o problemă mai largă: thread-ul nu primește resursa indiferent de prioritate, din cauza planificării inechitabile sau a secțiunilor critice lungi.

Poate apărea Starvation într-o aplicație monothread?

Nu, Starvation — este o problemă a multi-threading-ului. În codul monothread nu există concurență pentru resurse și planificare a thread-urilor. Cu toate acestea, Starvation poate apărea în codul asincron monothread (de exemplu, bucla de evenimente JavaScript), dacă o micro-sarcină amână la nesfârșit execuția altora prin setTimeout cu întârziere zero.

Cum este legat Java Memory Model de Starvation?

JMM (Java Memory Model) definește regulile de vizibilitate a modificărilor între thread-uri, dar nu garantează planificarea echitabilă. synchronized în conformitate cu JMM asigură consistenta secvențială — corectitudinea de bază — dar nu previne Starvation. Pentru echitate sunt necesare mecanisme suplimentare care nu fac parte din specificația JMM.

Ce este Starvation în thread-ul UI Android?

Thread-ul UI (Main Thread) nu poate înfometa în sensul clasic, deoarece are cea mai mare prioritate. Cu toate acestea, Starvation apare atunci când thread-ul UI așteaptă rezultatul unui thread de fundal înfometat. Scenariul tipic: AsyncTask sau corutina încarcă date, dar nu pot accesa baza de date din cauza concurenței cu alte thread-uri, iar UI îngheață în așteptare.

Cum să prevenim Starvation în Kotlin Coroutines?

În corutine, pentru prevenirea Starvation, utilizați limitedParallelism pe Dispatchers.IO pentru a evita epuizarea thread-urilor. Pentru sincronizare, aplicați Mutex din kotlinx.coroutines.sync — acesta suspendă corutina, nu blochează thread-ul, ceea ce reduce riscul de înfometare. Evitați runBlocking în corutine, deoarece poate captura thread-ul din pool și poate cauza Starvation altor corutine.

Concluzii

  • Starvation — situația în care un thread este pregătit pentru execuție dar nu primește resursa din cauza planificării inechitabile
  • Spre deosebire de Deadlock, în înfometare thread-ul este în starea RUNNABLE și se poate executa la scăderea sarcinii
  • Blocările inechitabile (synchronized) și utilizarea incorectă a priorităților — cauzele principale ale Starvation
  • Fair Lock (ReentrantLock cu indicatorul true) garantează ordinea de acces FIFO și elimină complet înfometarea
  • Structurile Lock-free (ConcurrentHashMap, AtomicReference) elimină Starvation la nivel de arhitectură
  • Thread Dump cu prelevări repetate și Java Flight Recorder — metode eficiente de diagnosticare a Starvation
  • Secțiunile critice scurte și ReadWriteLock reduc probabilitatea înfometării în sistemele cu sarcină ridicată

Vom dezvolta o aplicație mobilă la cheie

IT Sectr creează aplicații iOS și Android pentru startup-uri și afaceri din 2017. Vă vom consilia și vă vom propune cea mai bună soluție.

Discutați proiectul

Citiți și