Deadlock (qarşılıqlı bloklama) — bu, iki və ya daha çox ipin digər iştirakçılar tərəfindən tutulmuş resursların boşaldılmasını sonsuz gözlədiyi vəziyyətdir. Oracle Java Tutorials (2024)-a görə, Deadlock dairəvi gözləmə zamanı yaranır, hər bir ip digər ipə lazım olan blokadanı saxlayır. Xüsusi aşkarlama vasitələri olmadan Deadlock tətbiqin işləməsini görünən səhvlər olmadan tamamilə dayandırır.
Vacib məqamlar
Deadlock (qarşılıqlı bloklama) — bu, çoxiplikli proqramlaşdırmada iki və ya daha çox ipin bir-birini əbədi olaraq blokladığı vəziyyətdir. Hər bir ip digər ipə lazım olan resursu saxlayır və çatışmayan resursu əldə etməyi gözləyərək onu boşaltmır. Nəticədə iplərdən heç biri işləməyə davam edə bilməz.
Mobil inkişafda Deadlock xüsusilə kritikdir, çünki o, istisna və ya çökmələrə səbəb olmur. Tətbiq sadəcə istifadəçinin hərəkətlərinə cavab verməyi dayandırır (ANR — Application Not Responding) və yeganə çıxış yolu prosesin məcburi dayandırılmasıdır. Google məlumatlarına görə (Android Performance Patterns, 2023), Google Play Console-da ANR hesabatlarının təxminən 15%-i fon iplərindəki qarşılıqlı blokadalarla bağlıdır.
Deadlock-un digər rəqabət problemlərindən əsas fərqi — onun xarici müdaxilə olmadan geri döndürülməz olmasıdır. İplər resursları özləri boşaltmayacaqlar, çünki əməliyyat sistemi planlayıcısı blokadanı məcburi olaraq geri ala bilməz. Bu, Deadlock-u Livelock-dan fərqləndirir, burada iplər aktivdir, lakin faydalı iş görmürlər.
1971-ci ildə Edward G. Coffman Deadlock-un yaranması üçün zəruri olan dörd məcburi şərti formalaşdırdı. Onlardan heç olmasa biri yoxdursa, qarşılıqlı bloklama mümkün deyil. Bu şərtlər Coffman şərtləri kimi tanınır və bütün Deadlock qarşısının alınması alqoritmlərinin əsasını təşkil edir.
Resurs hər an yalnız bir ip tərəfindən əldə edilə bilər. Əgər resurs bir neçə ip tərəfindən eyni vaxtda oxunmağa imkan verirsə (məsələn, oxu rejimində ReadWriteLock), Deadlock yaranmır. Bu şərt Mutex və blokadaların təbiətindən qaynaqlanır.
Bir ip artıq əldə etdiyi resursu saxlayır və eyni zamanda başqa resursun əldə edilməsini gözləyir. Əgər ip növbəti resursu tələb etməzdən əvvəl cari resursu boşalda bilərsə (iki fazalı bloklama vasitəsilə), Hold and Wait şərti pozulur. Android-də bu, tez-tez ipin verilənlər bazası blokadasını saxlayıb SharedPreferences blokadasını əldə etməyə çalışdıqda özünü göstərir.
Əəliyyat sistemi blokadanı ipdən məcburi olaraq ala bilməz. Resurs yalnız ip özü onu boşaltdıqda azad edilir. Bəzi sistemlərdə (məsələn, SQLite WAL rejimi) ayrı-əməliyyatlar səviyyəsində məcburi müsadirə tətbiq edilir ki, bu da Deadlock riskini azaldır.
Hər birinin zəncirdəki növbəti tərəfindən saxlanılan resursu gözlədiyi qapalı ip zənciri mövcuddur. Məsələn, ip A resurs 1-i saxlayır və resurs 2-ni gözləyir, ip B resurs 2-ni saxlayır və resurs 1-i gözləyir. Bu, proqramçının memarlıq baxımından aradan qaldıra biləcəyi yeganə şərtdir — blokada iyerarxiyası vasitəsilə. Bütün iplər resursları ciddi şəkildə müəyyən edilmiş qlobal ardıcıllıqla əldə edərsə, dövr fiziki olaraq mümkün deyil.
Praktikada Android tətbiqlərində Deadlock ən çox müxtəlif səviyyəli blokadaların gizli kəsişməsi səbəbindən yaranır: verilənlər bazası blokadası (Room), SharedPreferences blokadası və yaddaşdakı kolleksiya blokadası. Bu blokadaların hər biri müxtəlif komponentlər tərəfindən idarə olunur və mərkəzləşdirilmiş əldə etmə protokolu olmadan proqramçılar bilmədən dövrlər yaradırlar.
Klassik qarşılıqlı bloklama nümunəsini nəzərdən keçirək — iki ip blokadaları müxtəlif ardıcıllıqla əldə edir. Birinci ip A resursunu bloklayıb B-ni əldə etməyə çalışarsa, ikincisi isə B-ni bloklayıb A-nı əldə etməyə çalışarsa, Deadlock yaranır.
class DeadlockExample {
private val lockA = Any()
private val lockB = Any()
fun operationA() {
synchronized(lockA) {
Thread.sleep(50) // işin simulyasiyası
synchronized(lockB) {
println("operationA yerinə yetirildi")
}
}
}
fun operationB() {
synchronized(lockB) { // tərs ardıcıllıq!
Thread.sleep(50)
synchronized(lockA) {
println("operationB yerinə yetirildi")
}
}
}
}
fun main() {
val ex = DeadlockExample()
Thread { ex.operationA() }.start()
Thread { ex.operationB() }.start()
// Tətbiq əbədi olaraq donacaq — Deadlock!
}
Bu nümunədə operationA lockA-nı əldə edir, operationB isə lockB-ni. Sonra hər biri ikinci blokadanı əldə etməyə çalışır — və hər ikisi sonsuz gözləyir. Proqram səhv mesajı olmadan donur. Düzəltmənin yeganə yolu bütün metodlarda blokadaların eyni ardıcıllıqla əldə edilməsini təmin etməkdir.
Bu üç rəqabət problemi tez-tez qarışdırılır, lakin onların mexanizmləri və nəticələri prinsipcə fərqlidir. Deadlock — tam dayanma, Starvation — resursun sonsuz gözlənilməsi, Livelock — aktiv hərəkətsizlik. Fərqləri anlamaq düzgün aradan qaldırma strategiyasını seçmək üçün kritik əhəmiyyət daşıyır.
| Xarakteristika | Deadlock | Starvation | Livelock |
|---|---|---|---|
| Iplərin vəziyyəti | Bloklanıb (BLOCKED) | Hazır (RUNNABLE) | Aktiv (RUNNABLE) |
| Işin yerinə yetirilməsi | Xeyr | Xeyr | Bəli, amma faydasız |
| Səbəb | Dairəvi gözləmə | Ədalətsiz planlaşdırma | Konfliktin səhv idarə edilməsi |
| Aşkarlama | Thread Dump, vaxt aşımı | Proqresin monitorinqi | Təkrarlanan cəhdlərin sayı |
Starvation (ac qalma) planlayıcının aşağı prioritetli ipin icrasını davamlı olaraq digərlərinin xeyrinə təxirə saldıqda yaranır. Deadlock-dan fərqli olaraq, ip bloklanmayıb — o, icraya hazırdır, lakin prosessor vaxtı almır. Android-də tipik ssenari — aşağı prioritetli fon ipi heç vaxt işləmir, əgər UI və Service ipləri daim aktivdirsə.
Livelock (aktiv bloklama) — iplərin bloklanmadığı, lakin bir-birinin hərəkətlərinə sonsuz reaksiya verdiyi, faydalı iş görmədiyi vəziyyətdir. Klassik analogiya — iki nəfər dəhlizdə qarşılaşır və hər ikisi eyni istiqamətdə hərəkət edərək yol verməyə çalışır. Deadlock-dan fərqli olaraq, Livelock-da iplər CPU istehlak edir və cihazın batareyasını boşaldır.
Thread Dump (iplərin zrzutu) — JVM və Android Runtime-da qarşılıqlı blokadaların aşkarlanmasının əsas vasitəsidir. Zrzut zamanı JVM monitorlar arasında asılılıq qrafını avtomatik təhlil edir və Deadlock dövrlərini qeyd edir. Android Studio-da iplərin zrzutunu Android Profiler və ya ADB Shell-dən kill -3 PID əmri ilə əldə etmək olar.
İcra zamanı Deadlock-un avtomatik aşkarlanması Watchdog taymerləri vasitəsilə həyata keçirilir. Əgər ip müəyyən edilmiş vaxt ərzində əməliyyatı tamamlamırsa, watchdog zrzutun başladılmasını təşkil edir və hesabatı Crash Reporting sisteminə (Firebase Crashlytics, Sentry) göndərir. Sentry məlumatlarına görə (Issue Resolution Report, 2024), watchdog-un konfiqurasiyası Deadlock diaqnostikası müddətini həftələrdən bir neçə saata endirir.
İnkişaf mərhələsində JetBrains-dən ThreadSafe statik analizatoru və Lock Checker modulu ilə Checker Framework effektivdir. Bu vasitələr mənbə kodu səviyyəsində blokadaların əldə edilmə ardıcıllığını təhlil edir və potensial dövrlər barədə xəbərdarlıq edir. Əlavə olaraq, Test-Driven Deadlock Detection — yüzlərlə ipdə müxtəlif bloklama ardıcıllığı ilə əməliyyatları işə salan stress testləri — tövsiyə olunur.
Xüsusi diqqətə layiqdir Cooperative Deadlock Detection — iplərin qlobal reyestr vasitəsilə əldə edilmiş blokadalar haqqında məlumat mübadiləsi etdiyi metod. Əgər ip potensial dövr aşkar edərsə, o, bütün resursları boşaldır və əməliyyatı təkrarlayır. Bu yanaşma paylanmış sistemlərdə (Apache ZooKeeper, Google Chubby) istifadə olunur və Jetpack Sync kimi kitabxanalar vasitəsilə tədricən mobil inkişafa tətbiq edilir.
Ən etibarlı üsul — bütün tətbiqdə blokadaların əldə edilməsinin qlobal ardıcıllığını təyin etməkdir. Əgər bütün iplər həmişə əvvəlcə kiçik nömrəli blokadanı, sonra isə böyük nömrəlini əldə edərsə, dairəvi gözləmə (Circular Wait şərti) mümkün deyil. Böyük layihələrdə ardıcıllıq sənədləşdirilir və kod nəzərdən keçirmə ilə yoxlanılır.
TryLock — ipi sonsuz bloklamayan, əksinə, blokada müəyyən edilmiş vaxtda əldə edilməzdisə false qaytaran bloklama metodudur. Java-da bu, ReentrantLock.tryLock(timeout, TimeUnit) vasitəsilə, Kotlin Coroutines-də isə vaxt aşımı ilə Mutex.withLock vasitəsilə həyata keçirilir. Uğursuz olduqda, ip bütün əldə edilmiş resursları boşaldır və cəhdi sonra təkrarlayır.
Bankir alqoritmi — Edsger Dijkstra tərəfindən təklif edilmiş Deadlock-un qarşısının alınmasının nəzəri metodudur. O, resursların bölgüsünü bank əməliyyatları kimi modelləşdirir: sistem resurs ayırmır, əgər bu təhlükəli vəziyyətə (deadlock) gətirib çıxara bilərsə. Praktikada alqoritm mobil inkişafda nadir hallarda tətbiq olunur, çünki iplərin maksimum tələblərini əvvəlcədən bilmək çətindir, lakin onun prinsipləri SQLite verilənlər bazalarında və fayl sistemlərində istifadə olunur.
Tez-tez verilən suallar
Xeyr, qarşılıqlı bloklama üçün ən azı iki ip lazımdır. Tək iplikli kodda bütün əməliyyatlar ardıcıl yerinə yetirilir, ona görə dairəvi gözləmə mümkün deyil. Lakin Deadlock fayl blokadaları və ya proseslərarası semaforlar istifadə edərkən proseslər arasında yarana bilər.
Korutinlərdə Deadlock dayandırılmış funksiyalar (suspend) səviyyəsində yaranır və OS ipini bloklamır, bu da onu daha az nəzərə çarpan edir. kotlinx.coroutines-dən Mutex dayandırıcıdır (suspending), ipi bloklamır, lakin korutin icra olunmur. Aşkarlama üçün kotlinx-coroutines-debug modulundan DebugProbes istifadə edin.
SQLite Deadlock iki verilənlər bazası əlaqəsi müxtəlif ardıcıllıqla əməliyyatlar yerinə yetirməyə çalışdıqda yaranır. SQLite belə vəziyyətləri aşkar edir və SQLITE_BUSY və ya SQLITE_LOCKED səhv kodu qaytarır. Android-də tək verilənlər bazası nüsxəsi və @Transaction ilə əməliyyatlar vasitəsilə Room-dan istifadə etmək tövsiyə olunur ki, bu da əlaqələrarası Deadlock-u aradan qaldırır.
Android Runtime ANR (Application Not Responding) yaranarkən işə düşən daxili Deadlock detektoruna malikdir. Sistem tətbiqin bütün iplərinin Thread Dump-ını təhlil edir və qarşılıqlı blokadaları qeyd edir. Nəticə /data/anr/traces.txt faylında və Google Play Console-un ANR Reports bölməsində mövcuddur.
Əvvəlcə, tətbiqin bütün iplərinin Thread Dump-ını əldə edin. Hər bir ipin hansı blokadaları saxladığını və hansıları əldə etməyə çalışdığını təhlil edin. Vaxt limiti aşıldıqda avtomatik zrzut ilə Watchdog taymeri tətbiq edin. Düzəlişdən sonra, təkrarlanmanın qarşısını almaq üçün CI kəmərində ThreadSafety lint qaydasını əlavə edin.
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