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 (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.
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.
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.
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.
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.
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.
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()
}
}
}
Üç 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.
| Parametre | Starvation | Deadlock | Livelock |
|---|---|---|---|
| İş parçacığı durumu | RUNNABLE | BLOCKED | RUNNABLE |
| İlerleme | Yok | Yok | Yok (aktif olmasına rağmen) |
| CPU kullanımı | Düşük | Minimum | Yüksek (%100'e kadar) |
| Neden | Adaletsiz zamanlama | Döngüsel bekleme | Çakışmaya aynı yanıt |
| Ana düzeltme | Fair Lock, kritik bölümleri kısaltma | Kilit hiyerarşisi | Yeniden 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.
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.
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 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.
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.
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
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.
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.
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.
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.
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
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.
Ayrıca okuyun