Deadlock (o‘zaro blokirovka) — bu ikki yoki undan ortiq oqimlarning boshqa ishtirokchilar tomonidan egallangan resurslarning bo‘shatilishini cheksiz kutish holatidir. Oracle Java Tutorials (2024)-ga ko‘ra, Deadlock aylanma kutishda yuzaga keladi, har bir oqim boshqa oqimga kerakli blokirovkani ushlab turadi. Maxsus aniqlash vositalarisiz, Deadlock ilovaning ishlashini ko‘rinadigan xatolarsiz to‘liq to‘xtatadi.
Muhim fikrlar
Deadlock (o‘zaro blokirovka) — ko‘p oqimli dasturlashda ikki yoki undan ortiq oqim bir-birini abadiy bloklaydigan holatdir. Har bir oqim boshqa oqimga kerakli resursni ushlab turadi va uni bo‘shatmaydi, yetishmayotgan resursni egallashni kutadi. Natijada, oqimlarning hech biri ishlashni davom ettira olmaydi.
Mobil ishlanmada Deadlock ayniqsa tanqidiydir, chunki u istisno yoki qulashlarga sabab bo‘lmaydi. Ilova shunchaki foydalanuvchi harakatlariga javob berishni to‘xtatadi (ANR — Application Not Responding) va yagona chiqish yo‘li — jarayonni majburiy tugatish. Google ma’lumotlariga ko‘ra (Android Performance Patterns, 2023), Google Play Console-dagi ANR hisobotlarining taxminan 15% fon oqimlaridagi o‘zaro blokirovkalar bilan bog‘liq.
Deadlock-ning boshqa raqobat muammolaridan asosiy farqi — uning tashqi aralashuvsiz qaytarilmasligi. Oqimlar resurslarni o‘z-o‘zidan bo‘shatmaydi, chunki operatsion tizim rejalashtiruvchisi blokirovkani majburiy qaytarib ololmaydi. Bu Deadlock-ni Livelock-dan farqlaydi, bu erda oqimlar faol, ammo foydali ish bajarmaydilar.
1971 yilda Edward G. Coffman Deadlock yuzaga kelishi uchun zarur bo‘lgan to‘rt majburiy shartni shakllantirdi. Agar ulardan hech bo‘lmasa biri bo‘lmasa, o‘zaro blokirovka mumkin emas. Bu shartlar Coffman shartlari sifatida tanilgan va barcha Deadlock oldini olish algoritmlarining asosini tashkil qiladi.
Resurs har bir momentda faqat bitta oqim tomonidan egallanishi mumkin. Agar resurs bir necha oqim tomonidan bir vaqtda o‘qishga ruxsat bersa (masalan, o‘qish rejimida ReadWriteLock), Deadlock yuzaga kelmaydi. Bu shart Mutex va blokirovkalarning tabiatidan kelib chiqadi.
Oqim allaqachon egallangan resursni ushlab turadi va bir vaqtda boshqa resursni egallashni kutadi. Agar oqim joriy resursni keyingi resursni so‘rashdan oldin bo‘shatishi mumkin bo‘lsa (ikki fazali blokirovka orqali), Hold and Wait sharti buziladi. Android-da bu ko‘pincha oqim ma’lumotlar bazasi blokirovkasini ushlab, SharedPreferences blokirovkasini egallashga harakat qilganda namoyon bo‘ladi.
Operatsion tizim blokirovkani oqimdan majburiy qaytarib ololmaydi. Resurs faqat oqim uni o‘zi bo‘shatganda ozod qilinadi. Ba‘zi tizimlarda (masalan, SQLite WAL rejimi) alohida operatsiyalar darajasida majburiy tortib olish joriy qilingan, bu Deadlock xavfini kamaytiradi.
Har biri zanjirdagi keyingi oqim tomonidan ushlab turilgan resursni kutayotgan yopiq oqim zanjiri mavjud. Masalan, oqim A resurs 1-ni ushlab turadi va resurs 2-ni kutadi, oqim B resurs 2-ni ushlab turadi va resurs 1-ni kutadi. Bu dasturchi arxitektura jihatdan bartaraf eta oladigan yagona shartdir — blokirovkalar ierarxiyasi orqali. Agar barcha oqimlar resurslarni qat’iy belgilangan global tartibda egallasa, aylanma jismonan mumkin emas.
Amalda, Android ilovalarida Deadlock ko‘pincha turli darajadagi blokirovkalarning yashirin kesishishi tufayli yuzaga keladi: ma’lumotlar bazasi blokirovkasi (Room), SharedPreferences blokirovkasi va xotiradagi kolleksiya blokirovkasi. Bu blokirovkalarning har biri turli komponentlar tomonidan boshqariladi va markazlashtirilgan egallash tartibi protokolisiz dasturchilar beixtiyor aylanalar yaratadilar.
Keling, o‘zaro blokirovkaning klassik misolini ko‘rib chiqaylik — ikki oqim blokirovkalarni turli tartibda egallaydi. Agar birinchi oqim A resursini bloklab B-ni egallashga harakat qilsa, ikkinchisi esa B-ni bloklab A-ni egallashga harakat qilsa, Deadlock yuzaga keladi.
class DeadlockExample {
private val lockA = Any()
private val lockB = Any()
fun operationA() {
synchronized(lockA) {
Thread.sleep(50) // ish simulyatsiyasi
synchronized(lockB) {
println("operationA bajarildi")
}
}
}
fun operationB() {
synchronized(lockB) { // teskari tartib!
Thread.sleep(50)
synchronized(lockA) {
println("operationB bajarildi")
}
}
}
}
fun main() {
val ex = DeadlockExample()
Thread { ex.operationA() }.start()
Thread { ex.operationB() }.start()
// Ilova abadiy qotib qoladi — Deadlock!
}
Ushbu misolda operationA lockA-ni egallaydi, operationB esa lockB-ni. So‘ngra har biri ikkinchi blokirovkani egallashga harakat qiladi — va ikkalasi ham cheksiz kutadi. Dastur xato xabarisiz qotib qoladi. Tuzatishning yagona usuli — barcha metodlarda blokirovkalarni egallashning bir xil tartibini ta’minlash.
Ushbu uch raqobat muammosi ko‘pincha chalkashtiriladi, ammo ularning mexanizmlari va oqibatlari tubdan farq qiladi. Deadlock — to‘liq to‘xtash, Starvation — resursning cheksiz kutishi, Livelock — faol harakatsizlik. Farqlarni tushunish to‘g‘ri bartaraf etish strategiyasini tanlash uchun juda muhimdir.
| Tavsifi | Deadlock | Starvation | Livelock |
|---|---|---|---|
| Oqimlar holati | Bloklangan (BLOCKED) | Tayyor (RUNNABLE) | Faol (RUNNABLE) |
| Ish bajarish | Yo‘q | Yo‘q | Ha, ammo foydasiz |
| Sabab | Aylanma kutish | Adolatsiz rejalashtirish | Mojaroni noto‘g‘ri boshqarish |
| Aniqlash | Thread Dump, vaqt chegaralari | Taraqqiyot monitoringi | Qayta urinishlar hisoblagichi |
Starvation (och qolish) rejalashtiruvchi past prioritetli oqimning bajarilishini doimiy ravishda boshqalar foydasiga kechiktirganda yuzaga keladi. Deadlock-dan farqli o‘laroq, oqim bloklanmagan — u bajarishga tayyor, ammo protsessor vaqti olmaydi. Android-da odatiy stsenariy — past prioritetli fon oqimi hech qachon bajarilmaydi, agar UI va Service oqimlari doimiy faol bo‘lsa.
Livelock (faol blokirovka) — oqimlar bloklanmagan, ammo bir-birining harakatlariga cheksiz javob beradigan, foydali ish bajarmaydigan holatdir. Klassik analogiya — ikki kishi koridorda uchrashib, ikkalasi ham bir tomonga harakatlanib, yo‘l berishga urinadi. Deadlock-dan farqli o‘laroq, Livelock-dagi oqimlar CPU iste’mol qiladi, qurilma batareyasini zaryadsizlantiradi.
Thread Dump (oqimlar dumpi) — JVM va Android Runtime-da o‘zaro blokirovkalarni aniqlashning asosiy vositasidir. Dump paytida JVM monitorlar o‘rtasidagi bog‘liqlik grafigini avtomatik tahlil qiladi va Deadlock aylanalarini belgilaydi. Android Studio-da oqimlar dumpini Android Profiler yoki ADB Shell-dan kill -3 PID buyrug‘i orqali olish mumkin.
Ishlash vaqtida Deadlock-ni avtomatik aniqlash Watchdog taymerlari orqali amalga oshiriladi. Agar oqim belgilangan vaqt ichida operatsiyani tugatmasa, watchdog dump boshlanishini tashkil qiladi va hisobotni Crash Reporting tizimiga (Firebase Crashlytics, Sentry) yuboradi. Sentry ma’lumotlariga ko‘ra (Issue Resolution Report, 2024), watchdog sozlamasi Deadlock diagnostika vaqtini haftalardan bir necha soatgacha qisqartiradi.
Ishlab chiqish bosqichida JetBrains-dan ThreadSafe statik analizatori va Lock Checker moduli bilan Checker Framework samarali. Bu vositalar manba kod darajasida blokirovkalarni egallash tartibini tahlil qiladi va potensial aylanalar haqida ogohlantiradi. Qo‘shimcha ravishda, Test-Driven Deadlock Detection — yuzlab oqimlarda turli blokirovka tartiblari bilan operatsiyalarni ishga tushiradigan stress testlar — tavsiya etiladi.
Alohida e’tiborga loyiq Cooperative Deadlock Detection — oqimlar global registr orqali egallangan blokirovkalar haqida ma’lumot almashadigan usuldir. Agar oqim potensial aylana aniqlasa, u barcha resurslarni bo‘shatadi va operatsiyani takrorlaydi. Bu yondashuv taqsimlangan tizimlarda (Apache ZooKeeper, Google Chubby) qo‘llaniladi va Jetpack Sync kabi kutubxonalar orqali asta-sekin mobil ishlanmaga joriy etilmoqda.
Eng ishonchli usul — butun ilovada blokirovkalarni egallashning global tartibini o‘rnatish. Agar barcha oqimlar avval kichik raqamli blokirovkani, so‘ngra katta raqamli blokirovkani egallasa, aylanma kutish (Circular Wait sharti) mumkin emas. Katta loyihalarda tartib hujjatlarda belgilanadi va kod ko‘rib chiqish orqali tekshiriladi.
TryLock — oqimni cheksiz bloklamaydigan, aksincha, blokirovka belgilangan vaqtda olinmasa false qaytaradigan blokirovka usulidir. Java-da bu ReentrantLock.tryLock(timeout, TimeUnit) orqali, Kotlin Coroutines-da vaqt chegarasi bilan Mutex.withLock orqali amalga oshiriladi. Muvaffaqiyatsizlikda, oqim barcha egallangan resurslarni bo‘shatadi va urinishni keyinroq takrorlaydi.
Bankir algoritmi — Edsger Dijkstra tomonidan taklif qilingan Deadlock oldini olishning nazariy usulidir. U resurslarni taqsimlashni bank operatsiyalari sifatida modellashtiradi: tizim resursni ajratmaydi, agar bu xavfli holatga (deadlock) olib kelishi mumkin bo‘lsa. Amalda algoritm mobil ishlanmada kam qo‘llaniladi, chunki oqimlarning maksimal ehtiyojlarini oldindan bilish murakkab, ammo uning tamoyillari SQLite ma’lumotlar bazalari va fayl tizimlarida qo‘llaniladi.
Tez-tez so‘raladigan savollar
Yo‘q, o‘zaro blokirovka uchun kamida ikki oqim kerak. Bir oqimli kodda barcha operatsiyalar ketma-ket bajariladi, shuning uchun aylanma kutish mumkin emas. Biroq Deadlock fayl blokirovkalari yoki jarayonlararo semaforlardan foydalanganda jarayonlar o‘rtasida yuzaga kelishi mumkin.
Korutinalarda Deadlock to‘xtatilgan funksiyalar (suspend) darajasida yuzaga keladi va OS oqimini bloklamaydi, bu uni kamroq sezilarli qiladi. kotlinx.coroutines-dan Mutex to‘xtatuvchi (suspending), u oqimni bloklamaydi, ammo korutina bajarilmaydi. Aniqlash uchun kotlinx-coroutines-debug modulidan DebugProbes foydalaning.
SQLite Deadlock ikki ma’lumotlar bazasi ulanishi turli tartibda operatsiyalarni bajarishga harakat qilganda yuzaga keladi. SQLite bunday vaziyatlarni aniqlaydi va SQLITE_BUSY yoki SQLITE_LOCKED xato kodini qaytaradi. Android-da yagona ma’lumotlar bazasi nusxasi va @Transaction orqali operatsiyalar bilan Room-dan foydalanish tavsiya etiladi, bu ulanishlararo Deadlock-ni bartaraf qiladi.
Android Runtime ANR (Application Not Responding) yaratilganda ishga tushadigan ichki Deadlock detektoriga ega. Tizim ilovaning barcha oqimlarining Thread Dump-ni tahlil qiladi va o‘zaro blokirovkalarni belgilaydi. Natija /data/anr/traces.txt va Google Play Console ning ANR Reports bo‘limida mavjud.
Avvalo, ilovaning barcha oqimlarining Thread Dump-ni oling. Har bir oqim qanday blokirovkalarni ushlab turganini va qaysilarini egallashga harakat qilayotganini tahlil qiling. Vaqt chegarasi oshib ketganda avtomatik dump bilan Watchdog taymerini joriy qiling. Tuzatishdan so‘ng, takrorlanishning oldini olish uchun CI quvuriga ThreadSafety lint qoidasini qo‘shing.
Xulosa
Biz kalit topshirig'i bilan mobil ilovani ishlab chiqamiz
IT Sectr 2017-yildan beri startaplar va korxonalar uchun iOS va Android ilovalarini yaratadi. Biz sizga maslahat beramiz va eng yaxshi yechimni taklif qilamiz.