Starvation (thread aclığı) — bu, thread-in işə davam etmək üçün lazım olan resursa giriş əldə edə bilmədiyi, baxmayaraq ki, özü icraya hazır olduğu vəziyyətdir. Baeldung (Java Thread Starvation, 2024)-yə görə, aclıq ədalətsiz planlaşdırma səbəbindən yaranır, aşağı prioritetli thread-lar daha yüksək prioritetli olanların xeyrinə daim təxirə salınır. Deadlock-dan fərqli olaraq, Starvation thread-i bloklamır — o RUNNABLE vəziyyətində qalır, lakin heç vaxt prosessor vaxtı almır.
Əsas məqamlar
Starvation (thread aclığı) — bu çoxthreadli proqramlaşdırma problemidir ki, thread tapşırığı yerinə yetirmək üçün lazım olan resursa giriş əldə edə bilmir, baxmayaraq ki, resurs başqa thread tərəfindən əbədi olaraq bloklanmayıb. Thread RUNNABLE vəziyyətindədir, lakin planlayıcı və ya sinxronizasiya mexanizmi sistematik olaraq onun icrasını digər thread-ların xeyrinə təxirə salır.
Mobil inkişafda Starvation qeyri-bərabər tapşırıq icrası kimi özünü göstərir: bəzi əməliyyatlar dərhal yerinə yetirilir, digərləri isə fəlakətli gecikmələrlə. Məsələn, məlumatları sinxronizasiya edən fon thread-i, UI thread və animasiya işləyiciləri daim onu qabaqlayırsa, verilənlər bazasına giriş əldə edə bilməz. Android Developer Blog (Performance Matters, 2023) məlumatına görə, Android-də buraxılmış kadrların (jank) təxminən 12%-i renderinqin asılı olduğu fon tapşırıqlarının Starvation səbəbindən baş verir.
Starvation-ın Deadlock-dan əsas fərqi — geri dönüşlülük. Sistem yükü azalarsa və ya prioritetlər yenidən bölüşdürülərsə, aclıq çəkən thread resursu alıb işi tamamlaya bilər. Lakin daimi yüksək yük şəraitində Starvation qeyri-müəyyən müddətə davam edə bilər, asılmış tətbiq təəssüratı yaradır.
Java və Kotlin-də synchronized — ədalətsiz mexanizmin klassik nümunəsidir. Yüksək rəqabət zamanı JVM eyni aktiv thread-lara qeyri-müəyyən müddətə kilidi verə bilər, digər thread-lar isə daim yarışda uduzur. Bu JVM-in xətası deyil, tətbiqin xüsusiyyətidir: ədalətsiz kilidlər girişin vahidliyi hesabına daha yüksək ötürmə qabiliyyətini təmin edir. 4-8 thread-li mobil tətbiqlər üçün bu problem xüsusilə aktualdır.
Müxtəlif thread prioritetlərinin təyin edilməsi aşağı prioritetli thread-ların Starvation-na səbəb ola bilər. Android Runtime-da Linux-un CFS (Completely Fair Scheduler) planlayıcısı prosessor vaxtını prioritetlərə nisbətdə bölüşdürür və yüksək prioritetli thread-lar daim aktivdirsə, aşağı prioritetlilər heç vaxt CPU ala bilməz. Google Android-də thread prioritetlərini dəyişməyi qətiyyən tövsiyə etmir — sistem özü onları idarə edir.
Əgər thread kilidi çox uzun müddət saxlayırsa (synchronized bloku daxilində ağır hesablamalar, şəbəkə sorğuları və ya fayl əməliyyatları yerinə yetirirsə), bu kilidi gözləyən digər thread-lar aclıq çəkir. Bu, xüsusilə Android-də təhlükəlidir, burada UI thread-də uzun əməliyyatlar ANR-yə səbəb olur, onların fon thread-larına köçürülməsi isə kritik seksiyaları optimallaşdırmadan Starvation problemini işçi thread-lara ötürür.
Bir nümunəyə baxaq, burada bir thread ədalətsiz planlaşdırma səbəbindən kilidi çox tez-tez ələ keçirir. Starvation yüksək prioritetli thread-in sonsuz dövrü vasitəsilə nümayiş etdirilir ki, bu da aşağı prioritetli thread-in ümumi resursa giriş əldə etməsinə mane olur.
class SharedResource {
private val lock = Any()
fun criticalSection(id: String) {
synchronized(lock) {
println("$id giriş əldə etdi")
Thread.sleep(10) // iş simulyasiyası
}
}
}
fun main() {
val resource = SharedResource()
// Yüksək prioritetli thread — daim aktiv
val highPriority = Thread {
while (true) {
resource.criticalSection("High")
}
}
// Aşağı prioritetli thread — heç vaxt giriş əldə edə bilməz
val lowPriority = Thread {
while (true) {
resource.criticalSection("Low")
}
}
highPriority.start()
lowPriority.start()
// "Low" heç vaxt mesaj çap etməyə bilər — Starvation!
}
Bu nümunədə highPriority thread-i daim kilidi ələ keçirir və onu yalnız 10 ms-ə buraxır. synchronized-in ədalətsiz xarakterinə görə JVM planlayıcısı yüksək ehtimalla kilidi yenidən həmin thread-ə verəcək — aşağı prioritetli thread aclıq çəkir. Həll yolu — fair flag-ı ilə ReentrantLock(true) istifadə etməkdir ki, bu da gözləmə növbəsində sıranı təmin edir.
Fair lock ilə düzəldilmiş versiya resursa girişin ədalətli bölüşdürülməsini təmin edir.
class FairSharedResource {
private val fairLock = ReentrantLock(true) // fair = true
fun criticalSection(id: String) {
fairLock.lock()
try {
println("$id giriş əldə etdi (fair)")
Thread.sleep(10)
} finally {
fairLock.unlock()
}
}
}
Üç klassik çoxthreadlilik problemi — Starvation, Deadlock və Livelock — tez-tez birləşdirilir, lakin onların mexanizmləri və aradan qaldırılması yolları fərqlidir. Starvation — thread hazırdır, lakin resurs almır. Deadlock — thread-lar tsiklik gözləmə ilə bloklanıb. Livelock — thread-lar aktivdir, lakin irəliləmir.
| Parametr | Starvation | Deadlock | Livelock |
|---|---|---|---|
| Thread vəziyyəti | RUNNABLE | BLOCKED | RUNNABLE |
| İrəliləmə | Yox | Yox | Yox (aktiv olsa da) |
| CPU istehlakı | Aşağı | Minimal | Yüksək (100%-ə qədər) |
| Səbəb | Ədalətsiz planlaşdırma | Tsiklik gözləmə | Münaqişəyə eyni reaksiya |
| Əsas həll | Fair Lock, kritik seksiyaların azaldılması | Kilid iyerarxiyası | Təkrarlama limiti, eksponensial backoff |
Starvation Deadlock-dan daha az kritik hesab olunur, çünki o ölümcül deyil — yük azaldıqda aclıq çəkən thread nəhayət yerinə yetiriləcək. Lakin yaddaş və CPU-nun məhdud olduğu Android tətbiqlərinin real istifadəsi şəraitində Starvation dəqiqələrlə davam edə bilər, qəbuledilməz UX yaradır.
Thread Dump qısa intervallarla təkrar götürməklə — aclığı aşkarlamağın əsas üsuludur. Əgər thread ardıcıl olaraq RUNNABLE vəziyyətindədirsə, lakin onun çağırış yığını bir neçə dump ərzində dəyişmirsə — bu Starvation-ın klassik əlamətidir. Android Studio-da bunun üçün Android Profiler istifadə olunur.
Avtomatik aşkarlama tapşırıqların icra vaxtının monitorinqi vasitəsilə mümkündür. Əgər proqnozlaşdırıla bilən icra müddəti olan tapşırıq (məsələn, 50 ms) 5 saniyə və ya daha çox yerinə yetirilirsə — Starvation ehtimalı yüksəkdir. Mobil tətbiqlərdə Firebase Performance Monitoring kritik seksiyalar üçün xüsusi izlər (custom traces) konfiqurasiya etməyə və həddi dəyərlər aşıldıqda bildirişlər almağa imkan verir.
synchronized blokları səbəbindən Starvation diaqnostikası üçün Java Flight Recorder (JFR) (Android-də OpenJDK API vasitəsilə mövcuddur) və ya Async Profiler istifadə edin. Bu alətlər hansı monitorların ən uzun gözləmə müddətinə malik olduğunu və hansı thread-ların hər bir monitor üçün rəqabət apardığını göstərir. JFR məlumatları IntelliJ IDEA Ultimate ilə quraşdırılmış profiler vasitəsilə inteqrasiya olunur.
ReentrantLock(true) thread-ların kilidi növbə sırası ilə (FIFO) almasını təmin edir. synchronized-dan fərqli olaraq, fair lock kilidi buraxmış thread-in dərhal onu yenidən ələ keçirməsi vəziyyətinə yol vermir. Bu, Starvation-ı tamamilə aradan qaldırır, baxmayaraq ki, növbənin saxlanması üçün əlavə xərclər səbəbindən ümumi ötürmə qabiliyyətini 10-20% azaldır.
Lock-free məlumat strukturları (ConcurrentHashMap, AtomicReference, LongAdder) tərifinə görə Starvation-ı istisna edir, çünki onlarda bir thread tərəfindən saxlanıla bilən kilidlər yoxdur. Bütün əməliyyatlar prosessorun CAS təlimatlarından istifadə edir ki, bu da sonlu sayda addımda ən azı bir thread-in irəliləməsini təmin edir. Mobil inkişaf üçün tapşırıq növbələri üçün ConcurrentLinkedQueue-ə üstünlük verin.
Kilidin saxlanma müddətinin minimallaşdırılması — Starvation riskini azaltmağın universal üsuludur. Ağır əməliyyatları (şəbəkə, disk giriş-çıxışı, mürəkkəb hesablamalar) synchronized blokundan kənara çıxarın. Oxucuların nadir yazıçılar səbəbindən aclıq çəkməməsi üçün ReadWriteLock istifadə edin. Kotlin Coroutines kitabxanası OS thread-ni bloklamayan suspending mexanizmi ilə Mutex təqdim edir.
Condition.await() və signal() ehtiyatla istifadə edilməlidir: Condition-da gözləyən thread digər thread-larla birlikdə oyanır (spurious wakeup) və hamısı kilid üçün rəqabət aparır. Əgər bir thread await-dən sonra dərhal gözləməyə qayıdırsa, digərləri isə kilidi ələ keçirməyə nail olursa — aclıq çəkən thread sonsuz olaraq oyanıb yuxuya gedə bilər. Şərti həmişə while dövrəsində yoxlayın, if-də deyil, təkrar yoxlamanı təmin etmək üçün.
Tez-tez verilən suallar
Priority Inversion — bu, aşağı prioritetli thread-in yüksək prioritetli thread üçün lazım olan kilidi saxladığı vəziyyətdir. Nəticədə yüksək prioritetli thread aşağı prioritetlini gözləyir — prioritetlər tərsinə çevrilir. Starvation daha geniş problemdir: thread prioritetdən asılı olmayaraq, ədalətsiz planlaşdırma və ya uzun kritik seksiyalar səbəbindən resurs almır.
Xeyr, Starvation — çoxthreadlilik problemidir. Təkthreadli kodda resurslar üçün rəqabət və thread planlaşdırması yoxdur. Lakin Starvation asinxron təkthreadli kodda (məsələn, JavaScript event loop) yarana bilər, əgər bir mikrotapşırıq setTimeout vasitəsilə sıfır gecikmə ilə digərlərinin icrasını sonsuz təxirə salırsa.
JMM (Java Memory Model) thread-lar arasında dəyişikliklərin görünmə qaydalarını müəyyən edir, lakin ədalətli planlaşdırmanı təmin etmir. JMM-ə uyğun olaraq synchronized sequential consistency — əsas düzgünlüyü təmin edir, lakin Starvation-ın qarşısını almır. Ədalət üçün JMM spesifikasiyasına daxil olmayan əlavə mexanizmlər tələb olunur.
UI thread (Main Thread) klassik mənada aclıq çəkə bilməz, çünki ən yüksək prioritetə malikdir. Lakin Starvation UI thread aclıq çəkən fon thread-in nəticəsini gözlədikdə yaranır. Tipik ssenari: AsyncTask və ya korutin məlumatları yükləyir, lakin digər thread-larla rəqabət səbəbindən verilənlər bazasına giriş əldə edə bilmir və UI gözləmədə donur.
Korutinlərdə Starvation-ın qarşısını almaq üçün Dispatchers.IO-da limitedParallelism istifadə edin ki, thread-ların tükənməsindən qaçınasınız. Sinxronizasiya üçün kotlinx.coroutines.sync-dən Mutex tətbiq edin — o, korutini dayandırır, thread-i bloklamır, bu da aclıq riskini azaldır. Korutinlərdə runBlocking-dən çəkinin, çünki o, hovuz thread-ni ələ keçirib digər korutinlərin Starvation-na səbəb ola bilər.
Nəticə
Açar təslim mobil tətbiq hazırlayacağıq
IT Sectr 2017-ci ildən startaplar və bizneslər üçün iOS və Android tətbiqləri yaradır. Sizə məsləhət verəcəyik və ən yaxşı həlli təklif edəcəyik.
Həm də oxuyun