Mobil uygulamalarda Starvation — özü, nedenleri ve iş parçacığı açlığını önleme yöntemleri

Yazar: IT Sectr Yayınlanma: 2026-03-18 Okuma süresi: 10 dk

Starvation (iş parçacığı açlığı), bir iş parçacığının yürütülmeye hazır olmasına rağmen çalışmasına devam etmek için gerekli kaynağa erişemediği bir durumdur. Baeldung (Java Thread Starvation, 2024)'a göre, açlık, düşük öncelikli iş parçacıklarının sürekli olarak daha yüksek öncelikli olanlar lehine ertelendiği adaletsiz zamanlama nedeniyle ortaya çıkar. Deadlock'tan farklı olarak, Starvation iş parçacığını bloke etmez — RUNNABLE durumunda kalır ancak asla CPU süresi alamaz.

Ana Noktalar

  • Starvation — bir iş parçacığının yürütülmeye hazır olmasına rağmen bir kaynağa erişemediği durum
  • Deadlock'tan farklı olarak, açlık çeken iş parçacığı RUNNABLE durumunda kalır — bloke değildir, ancak ilerlemez
  • Adaletsiz zamanlama (örneğin, synchronized ile senkronizasyon) — JVM'de Starvation'ın ana nedeni
  • Fair Lock (ReentrantLock(true)) adil FIFO kilit edinme sırasını garanti eder
  • Thread Priority mobil geliştirmede değiştirilmemesi önerilir — Android Runtime öncelikleri kendisi yönetir

Starvation Nedir?

Starvation (iş parçacığı açlığı), bir iş parçacığının görevini tamamlamak için gerekli kaynağa erişemediği, ancak kaynağın başka bir iş parçacığı tarafından kalıcı olarak kilitlenmediği bir çoklu iş parçacığı programlama sorunudur. İş parçacığı RUNNABLE durumundadır, ancak zamanlayıcı veya senkronizasyon mekanizması, diğer iş parçacıkları lehine yürütmesini sistematik olarak erteler.

Mobil geliştirmede Starvation, eşit olmayan görev yürütmesi olarak kendini gösterir: bazı işlemler anında yürütülürken, diğerleri felaket düzeyinde gecikmeler yaşar. Örneğin, bir arka plan veri senkronizasyon iş parçacığı, UI iş parçacığı ve animasyon işleyicileri tarafından sürekli geçiliyorsa veritabanına asla erişemeyebilir. Android Developer Blog'a (Performance Matters, 2023) göre, Android'deki atlanan karelerin (jank) yaklaşık %12'si, işlemenin bağlı olduğu arka plan görevlerinin Starvation'ından kaynaklanır.

Starvation ve Deadlock arasındaki temel fark geri döndürülebilirliktir. Sistem yükü azalırsa veya öncelikler yeniden dağıtılırsa, açlık çeken iş parçacığı kaynağı alabilir ve işini tamamlayabilir. Ancak, sürekli yüksek yük altında, Starvation süresiz olarak devam edebilir ve donmuş bir uygulama izlenimi yaratabilir.

İş Parçacığı Açlığının Nedenleri

Adaletsiz Kilitler (Non-Fair Locks)

Java ve Kotlin'deki synchronized, adaletsiz bir mekanizmanın klasik bir örneğidir. Yüksek rekabet altında, JVM sürekli olarak aynı aktif iş parçacıklarına kilit verebilirken, diğer iş parçacıkları sürekli olarak yarışı kaybeder. Bu bir JVM hatası değil, bir tasarım ödünleşimidir: adaletsiz kilitler, erişim adaleti pahasına daha yüksek verim sağlar. 4-8 iş parçacığına sahip mobil uygulamalar için bu sorun özellikle önemlidir.

Önceliklerin Yanlış Kullanımı

Farklı iş parçacığı öncelikleri ayarlamak, düşük öncelikli iş parçacıklarının Starvation'ına yol açabilir. Android Runtime'da, Linux CFS (Completely Fair Scheduler) zamanlayıcısı CPU süresini önceliklerle orantılı olarak dağıtır ve yüksek öncelikli iş parçacıkları sürekli aktifse, düşük öncelikli iş parçacıkları asla CPU süresi alamayabilir. Google, Android'de iş parçacığı önceliklerinin değiştirilmesini kesinlikle önermez — sistem bunları kendisi yönetir.

Uzun Kritik Bölümler

Bir iş parçacığı bir kilidi çok uzun süre tutarsa (synchronized bloğu içinde ağır hesaplamalar, ağ istekleri veya dosya işlemleri gerçekleştirirse), bu kilidi bekleyen diğer iş parçacıkları açlık çeker. Bu, Android'de özellikle tehlikelidir, çünkü UI iş parçacığındaki uzun işlemler ANR'ye neden olur ve kritik bölümleri optimize etmeden bunları arka plan iş parçacıklarına taşımak, Starvation sorununu yalnızca çalışan iş parçacıklarına aktarır.

Kotlin Kodunda Starvation Örneği

Adaletsiz zamanlama nedeniyle bir iş parçacığının bir kilidi çok sık edindiği bir örneği inceleyelim. Starvation, düşük öncelikli bir iş parçacığının paylaşılan bir kaynağa erişmesini engelleyen yüksek öncelikli bir iş parçacığının sonsuz döngüsü aracılığıyla gösterilir.

kotlin
class SharedResource {
    private val lock = Any()

    fun criticalSection(id: String) {
        synchronized(lock) {
            println("$id erişim sağladı")
            Thread.sleep(10)  // iş simülasyonu
        }
    }
}

fun main() {
    val resource = SharedResource()

    // Yüksek öncelikli iş parçacığı — sürekli aktif
    val highPriority = Thread {
        while (true) {
            resource.criticalSection("High")
        }
    }

    // Düşük öncelikli iş parçacığı — asla erişemeyebilir
    val lowPriority = Thread {
        while (true) {
            resource.criticalSection("Low")
        }
    }

    highPriority.start()
    lowPriority.start()
    // "Low" asla mesaj yazdıramayabilir — Starvation!
}

Bu örnekte, highPriority iş parçacığı sürekli olarak kilidi alır ve yalnızca 10 ms için serbest bırakır. synchronized'ın adaletsiz doğası nedeniyle, JVM zamanlayıcısı büyük olasılıkla kilidi az önce serbest bırakan aynı iş parçacığına tekrar verecektir — düşük öncelikli iş parçacığı açlık çeker. Çözüm, FIFO bekleme sırasını garanti eden fair bayrağıyla ReentrantLock(true) kullanmaktır.

Adil kilit ile düzeltilmiş sürüm, kaynağa adil erişim dağıtımını sağlar.

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

    fun criticalSection(id: String) {
        fairLock.lock()
        try {
            println("$id erişim sağladı (fair)")
            Thread.sleep(10)
        } finally {
            fairLock.unlock()
        }
    }
}

Starvation vs Deadlock vs Livelock

Üç klasik çoklu iş parçacığı sorunu — Starvation, Deadlock ve Livelock — sıklıkla bir arada gruplanır, ancak mekanizmaları ve çözümleri farklıdır. Starvation — iş parçacığı hazır ancak kaynağı alamaz. Deadlock — iş parçacıkları döngüsel bekleme nedeniyle bloke olmuştur. Livelock — iş parçacıkları aktif ancak ilerlemez.

ParametreStarvationDeadlockLivelock
İş parçacığı durumuRUNNABLEBLOCKEDRUNNABLE
İlerlemeYokYokYok (aktif olmasına rağmen)
CPU kullanımıDüşükMinimumYüksek (%100'e kadar)
NedenAdaletsiz zamanlamaDöngüsel beklemeÇakışmaya aynı yanıt
Ana düzeltmeFair Lock, kritik bölümleri kısaltmaKilit hiyerarşisiYeniden deneme sınırı, exponential backoff

Starvation, Deadlock'tan daha az kritik olarak kabul edilir çünkü ölümcül değildir — azaltılmış yük altında, açlık çeken iş parçacığı sonunda yürütülecektir. Ancak, bellek ve CPU'nun sınırlı olduğu gerçek Android kullanımında, Starvation dakikalarca sürebilir ve kabul edilemez bir kullanıcı deneyimi yaratabilir.

Starvation Nasıl Tespit Edilir

Thread Dump kısa aralıklarla tekrar tekrar alınır — açlığı tespit etmenin temel yöntemi. Bir iş parçacığı sürekli olarak RUNNABLE durumundaysa ancak çağrı yığını birden çok döküm boyunca değişmiyorsa — bu Starvation'ın klasik bir işaretidir. Android Studio'da, zaman içinde iş parçacığı durumu kaydı için Android Profiler'ı kullanın.

Görevlerin yürütme süresi izlenmesi yoluyla otomatik tespit mümkündür. Tahmin edilebilir bir yürütme süresine (örneğin, 50 ms) sahip bir görev 5 saniye veya daha uzun sürüyorsa — yüksek olasılıkla Starvation vardır. Mobil uygulamalarda, Firebase Performance Monitoring, kritik bölümler için özel izlemeler ayarlamanıza ve eşikler aşıldığında bildirim almanıza olanak tanır.

synchronized bloklarının neden olduğu Starvation'ı teşhis etmek için Java Flight Recorder (JFR) (OpenJDK API aracılığıyla Android'de kullanılabilir) veya Async Profiler kullanın. Bu araçlar, hangi monitörlerin en yüksek bekleme sürelerine sahip olduğunu ve hangi iş parçacıklarının her monitör için rekabet ettiğini gösterir. JFR verileri, yerleşik profil oluşturucu aracılığıyla IntelliJ IDEA Ultimate ile entegre olur.

İş Parçacığı Açlığını Önleme Yöntemleri

Fair Lock (true bayrağıyla ReentrantLock)

ReentrantLock(true), iş parçacıklarının kilidi FIFO sırasıyla almasını garanti eder. synchronized'dan farklı olarak, adil bir kilit, kilidi az önce serbest bırakan bir iş parçacığının hemen tekrar almasına izin vermez. Bu, Starvation'ı tamamen ortadan kaldırır, ancak kuyruğu korumanın ek yükü nedeniyle genel verimi %10–20 oranında azaltır.

Kilitsiz atomik yapılar

Kilitsiz veri yapıları (ConcurrentHashMap, AtomicReference, LongAdder) tanım gereği Starvation'ı ortadan kaldırır, çünkü bir iş parçacığı tarafından tutulabilecek kilitleri yoktur. Tüm işlemler, sınırlı sayıda adımda en az bir iş parçacığının ilerlemesini garanti eden CPU CAS talimatlarını kullanır. Mobil geliştirme için, görev kuyrukları için ConcurrentLinkedQueue'yi tercih edin.

Kısa kritik bölümler

Kilit tutma süresini en aza indirme, Starvation riskini azaltmanın evrensel bir yoludur. Ağır işlemleri (ağ, disk G/Ç, karmaşık hesaplamalar) synchronized bloklarının dışına taşıyın. Okuyucuların seyrek yazıcılar nedeniyle açlık çekmemesi gereken senaryolar için ReadWriteLock kullanın. Kotlin Coroutines kitaplığı, bir işletim sistemi iş parçacığını bloke etmeyen askıya alma mekanizmasına sahip Mutex sağlar.

Koşul değişkenleri ve sinyaller

Condition.await() ve signal() dikkatli kullanılmalıdır: bir Condition'da bekleyen bir iş parçacığı diğer iş parçacıklarıyla birlikte uyanır (spurious wakeup) ve tümü kilit için rekabet eder. await'tan hemen sonra beklemeye dönen bir iş parçacığı varken diğerleri kilidi almayı başarırsa, açlık çeken iş parçacığı süresiz olarak uyanıp uyuyabilir. Yeniden kontrolü garanti etmek için koşulu her zaman if yerine while döngüsünde kontrol edin.

Sıkça Sorulan Sorular

Starvation ve Priority Inversion arasındaki fark nedir?

Priority Inversion (öncelik tersine çevrilmesi), düşük öncelikli bir iş parçacığının yüksek öncelikli bir iş parçacığının ihtiyaç duyduğu bir kilidi tuttuğu durumdur. Sonuç olarak, yüksek öncelikli iş parçacığı düşük öncelikli olanı bekler — öncelikler tersine döner. Starvation daha geniş bir sorundur: bir iş parçacığı, öncelikten bağımsız olarak, adaletsiz zamanlama veya uzun kritik bölümler nedeniyle bir kaynağı alamaz.

Tek iş parçacıklı bir uygulamada Starvation oluşabilir mi?

Hayır, Starvation çoklu iş parçacığı sorunudur. Tek iş parçacıklı kodda kaynak rekabeti veya iş parçacığı zamanlaması yoktur. Ancak, asenkron tek iş parçacıklı kodda (örneğin, JavaScript olay döngüsü), bir mikro görev sıfır gecikmeli setTimeout aracılığıyla diğerlerinin yürütmesini süresiz olarak ertelerse Starvation oluşabilir.

Java Bellek Modeli Starvation ile nasıl ilişkilidir?

JMM (Java Memory Model), iş parçacıkları arasındaki değişikliklerin görünürlüğü için kurallar tanımlar ancak adil zamanlamayı garanti etmez. synchronized, JMM'ye göre sıralı tutarlılık — temel doğruluk — sağlar, ancak Starvation'ı önlemez. Adalet, JMM'de belirtilmeyen ek mekanizmalar gerektirir.

Android UI iş parçacığında Starvation nedir?

UI iş parçacığı (Main Thread) klasik anlamda açlık çekemez çünkü en yüksek önceliğe sahiptir. Ancak, UI iş parçacığı açlık çeken bir arka plan iş parçacığından sonuç beklediğinde Starvation oluşur. Tipik bir senaryo: bir AsyncTask veya coroutine veri yükler ancak diğer iş parçacıklarıyla rekabet nedeniyle veritabanına erişemez ve UI beklerken donar.

Kotlin Coroutines'de Starvation nasıl önlenir?

Coroutine'lerde Starvation'ı önlemek için, iş parçacığı tükenmesini önlemek amacıyla Dispatchers.IO'da limitedParallelism kullanın. Senkronizasyon için kotlinx.coroutines.sync'den Mutex kullanın — iş parçacığını bloke etmek yerine coroutine'i askıya alır, açlık riskini azaltır. Coroutine'lerde runBlocking'den kaçının, çünkü bir havuz iş parçacığını yakalayabilir ve diğer coroutine'lerin Starvation'ına neden olabilir.

Özet

  • Starvation — iş parçacığının yürütülmeye hazır olduğu ancak adaletsiz zamanlama nedeniyle kaynak alamadığı durum
  • Deadlock'tan farklı olarak, açlık çeken iş parçacığı RUNNABLE durumunda kalır ve yük azaldığında yürütülebilir
  • Adaletsiz kilitler (synchronized) ve önceliklerin yanlış kullanımı Starvation'ın ana nedenleri
  • Fair Lock (true bayrağıyla ReentrantLock) FIFO erişim sırasını garanti eder ve açlığı tamamen ortadan kaldırır
  • Kilitsiz yapılar (ConcurrentHashMap, AtomicReference) mimari düzeyde Starvation'ı ortadan kaldırır
  • Thread Dump tekrarlanan yakalamalarla ve Java Flight Recorder Starvation teşhisinde etkili yöntemlerdir
  • Kısa kritik bölümler ve ReadWriteLock, yüksek yüklü sistemlerde açlık olasılığını azaltır

Anahtar teslim bir mobil uygulama geliştireceğiz

IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.

Projeyi tartış

Ayrıca okuyun