Deadlock (mutual blocking) — ay isang estado kung saan dalawa o higit pang mga thread ang walang katapusang naghihintay ng pagpapalaya ng mga mapagkukunang kinuha ng ibang mga kalahok. Ayon sa Oracle Java Tutorials (2024), ang Deadlock ay nangyayari sa paikot na paghihintay, kapag ang bawat thread ay may hawak na lock na kailangan ng isa pang thread. Kung walang espesyal na paraan ng pagtuklas, Deadlock ang ganap na humihinto sa pagpapatakbo ng application nang walang nakikitang mga error.
Mga Pangunahing Punto
Deadlock (mutual blocking) — ay isang sitwasyon sa multi-thread programming kung saan dalawa o higit pang mga thread ang permanenteng nagba-block sa isa’t isa. Ang bawat thread ay may hawak na mapagkukunang kailangan ng isa pang thread at hindi ito pinapalaya, naghihintay na makuha ang nawawalang mapagkukunan. Bilang resulta, wala sa mga thread ang maaaring magpatuloy sa pagpapatakbo.
Sa mobile development, ang Deadlock ay partikular na kritikal dahil hindi ito nagdudulot ng mga exception o crash. Ang application ay humihinto na lamang sa pagtugon sa mga aksyon ng gumagamit (ANR — Application Not Responding), at ang tanging paraan ay sapilitang pagtatapos ng proseso. Ayon sa data ng Google (Android Performance Patterns, 2023), humigit-kumulang 15% ng mga ulat ng ANR sa Google Play Console ay nauugnay sa mutual blocking sa mga background thread.
Ang pangunahing pagkakaiba ng Deadlock mula sa iba pang mga problema ng concurrency — ang hindi pagbabalik nito nang walang panlabas na interbensyon. Ang mga thread ay hindi magpapalaya ng mga mapagkukunan nang mag-isa, dahil ang scheduler ng operating system ay hindi maaaring sapilitang bawiin ang lock. Ito ang nagpapaiba ng Deadlock sa Livelock, kung saan ang mga thread ay aktibo ngunit hindi gumagawa ng kapaki-pakinabang na trabaho.
Noong 1971, si Edward G. Coffman ay nagbalangkas ng apat na sapilitang kondisyon na kinakailangan para sa paglitaw ng Deadlock. Kung kahit isa sa mga ito ay wala, ang mutual blocking ay imposible. Ang mga kondisyong ito ay kilala bilang mga kondisyon ng Coffman at nasa puso ng lahat ng algorithm ng pag-iwas sa Deadlock.
Ang mapagkukunan ay maaaring makuha lamang ng isang thread sa bawat sandali. Kung pinapayagan ng mapagkukunan ang sabay-sabay na pagbasa ng maraming thread (halimbawa, ReadWriteLock sa mode ng pagbasa), hindi nangyayari ang Deadlock. Ang kondisyong ito ay nagmumula sa mismong kalikasan ng Mutex at mga lock.
Ang isang thread ay may hawak na nakuhang mapagkukunan at sabay na naghihintay ng pagkuha ng isa pang mapagkukunan. Kung ang thread ay maaaring magpalaya sa kasalukuyang mapagkukunan bago humiling ng susunod (sa pamamagitan ng two-phase locking), ang kondisyon ng Hold and Wait ay nilalabag. Sa Android, ito ay madalas na nagpapakita kapag ang isang thread ay may hawak na database lock at sinusubukang makuha ang SharedPreferences lock.
Ang operating system ay hindi maaaring sapilitang tanggalin ang lock mula sa thread. Ang mapagkukunan ay pinapalaya lamang kapag ang thread mismo ang naglabas nito. Sa ilang mga sistema (halimbawa, SQLite WAL mode), ang sapilitang preemption ay ipinatutupad sa antas ng mga indibidwal na operasyon, na nagbabawas ng panganib ng Deadlock.
Mayroong saradong kadena ng mga thread, bawat isa ay naghihintay ng mapagkukunang hawak ng susunod sa kadena. Halimbawa, ang thread A ay may hawak na mapagkukunan 1 at naghihintay ng mapagkukunan 2, ang thread B ay may hawak na mapagkukunan 2 at naghihintay ng mapagkukunan 1. Ito ang tanging kondisyon na maaaring alisin ng developer sa arkitektural na paraan — sa pamamagitan ng hierarkiya ng mga lock. Kung lahat ng thread ay kumukuha ng mga mapagkukunan sa isang mahigpit na itinakdang pandaigdigang pagkakasunud-sunod, ang cycle ay pisikal na imposible.
Sa pagsasagawa, sa mga Android application, ang Deadlock ay kadalasang nangyayari dahil sa implicit na pagtawid ng mga lock ng iba’t ibang antas: database lock (Room), SharedPreferences lock, at memory collection lock. Ang bawat isa sa mga lock na ito ay pinamamahalaan ng iba’t ibang mga component, at kung walang sentralisadong protocol ng pagkakasunud-sunod ng pagkuha, ang mga developer ay hindi sinasadyang lumikha ng mga cycle.
Tingnan natin ang isang klasikong halimbawa ng mutual blocking — dalawang thread ang kumukuha ng mga lock sa magkaibang pagkakasunud-sunod. Kung ang unang thread ay nag-block ng mapagkukunan A at sumusubok na makuha ang B, at ang pangalawa ay nag-block ng B at sumusubok na makuha ang A, nangyayari ang Deadlock.
class DeadlockExample {
private val lockA = Any()
private val lockB = Any()
fun operationA() {
synchronized(lockA) {
Thread.sleep(50) // simulasyon ng trabaho
synchronized(lockB) {
println("operationA nakumpleto")
}
}
}
fun operationB() {
synchronized(lockB) { // baligtad na pagkakasunud-sunod!
Thread.sleep(50)
synchronized(lockA) {
println("operationB nakumpleto")
}
}
}
}
fun main() {
val ex = DeadlockExample()
Thread { ex.operationA() }.start()
Thread { ex.operationB() }.start()
// Ang application ay mag-freeze nang tuluyan — Deadlock!
}
Sa halimbawang ito, operationA ay kumukuha ng lockA, at operationB ay kumukuha ng lockB. Pagkatapos ang bawat isa ay sumusubok na makuha ang pangalawang lock — at parehong naghihintay nang walang katapusan. Ang programa ay nag-freeze nang walang mensahe ng error. Ang tanging paraan ng pag-aayos ay garantiya ang parehong pagkakasunud-sunod ng pagkuha ng lock sa lahat ng mga pamamaraan.
Ang tatlong problemang ito ng concurrency ay madalas na napagkakamalan, ngunit ang kanilang mga mekanismo at bunga ay panimula na naiiba. Deadlock — kumpletong pagtigil, Starvation — walang katapusang paghihintay para sa mapagkukunan, Livelock — aktibong kawalan ng gawa. Ang pag-unawa sa mga pagkakaiba ay kritikal para sa pagpili ng tamang estratehiya ng pag-aalis.
| Katangian | Deadlock | Starvation | Livelock |
|---|---|---|---|
| Estado ng mga thread | Naka-block (BLOCKED) | Handa (RUNNABLE) | Aktibo (RUNNABLE) |
| Pagpapatupad ng trabaho | Hindi | Hindi | Oo, ngunit walang silbi |
| Sanhi | Paikot na paghihintay | Hindi patas na pag-iiskedyul | Maling paghawak ng conflict |
| Pagtuklas | Thread Dump, mga timeout | Pagmomonitor ng progreso | Bilang ng mga pagsubok muli |
Ang Starvation (gutom) ay nangyayari kapag ang scheduler ay patuloy na ipinagpapaliban ang pagpapatupad ng isang thread na may mababang priyoridad pabor sa iba. Hindi tulad ng Deadlock, ang thread ay hindi naka-block — ito ay handa nang isagawa ngunit hindi nakakakuha ng oras ng CPU. Sa Android, ang tipikal na senaryo ay isang background thread na may mababang priyoridad na hindi kailanman naisasagawa kung ang UI at Service thread ay patuloy na aktibo.
Livelock (aktibong lock) — sitwasyon kung saan ang mga thread ay hindi naka-block, ngunit walang katapusang tumutugon sa mga aksyon ng isa’t isa, nang hindi gumagawa ng kapaki-pakinabang na trabaho. Ang klasikong analohiya — dalawang tao ay nagkikita sa koridor at parehong sumusubok na magbigay-daan, gumagalaw sa parehong direksyon. Hindi tulad ng Deadlock, ang mga thread sa Livelock ay kumokonsumo ng CPU, nagpapababa ng baterya ng device.
Thread Dump (dump ng mga thread) — pangunahing kasangkapan para sa pagtuklas ng mutual blocking sa JVM at Android Runtime. Sa oras ng dump, awtomatikong sinusuri ng JVM ang dependency graph sa pagitan ng mga monitor at minamarkahan ang mga Deadlock cycle. Sa Android Studio, ang thread dump ay maaaring makuha sa pamamagitan ng Android Profiler o command na kill -3 PID mula sa ADB Shell.
Ang awtomatikong pagtuklas ng Deadlock sa runtime ay naisasagawa sa pamamagitan ng Watchdog timer. Kung ang isang thread ay hindi natapos ang operasyon sa loob ng itinakdang timeout, ang watchdog ay nagpapasimula ng paggawa ng dump at nagpapadala ng ulat sa Crash Reporting system (Firebase Crashlytics, Sentry). Ayon sa data ng Sentry (Issue Resolution Report, 2024), ang pagsasaayos ng watchdog ay nagbabawas ng oras ng diagnosis ng Deadlock mula sa mga linggo hanggang sa ilang oras.
Sa yugto ng pag-unlad, epektibo ang static analyzer na ThreadSafe mula sa JetBrains at Checker Framework na may Lock Checker module. Ang mga tool na ito ay sumusuri sa pagkakasunud-sunod ng pagkuha ng lock sa antas ng source code at nagbabala tungkol sa mga potensyal na cycle. Dagdag pa, inirerekomenda ang Test-Driven Deadlock Detection — mga stress test na nagpapatakbo ng mga operasyon na may iba’t ibang pagkakasunod-sunod ng pag-lock sa daan-daang thread.
Ang espesyal na atensyon ay nararapat sa Cooperative Deadlock Detection — pamamaraan kung saan ang mga thread ay nagpapalitan ng impormasyon tungkol sa mga nakuhang lock sa pamamagitan ng global registry. Kung ang isang thread ay nakakakita ng potensyal na cycle, pinapalaya nito ang lahat ng mapagkukunan at inuulit ang operasyon. Ang approach na ito ay ginagamit sa distributed systems (Apache ZooKeeper, Google Chubby) at unti-unting ipinakikilala sa mobile development sa pamamagitan ng mga library tulad ng Jetpack Sync.
Ang pinaka-maaasahang paraan — magtatag ng pandaigdigang pagkakasunud-sunod ng pagkuha ng lock sa buong application. Kung lahat ng thread ay unang kumukuha ng lock na may mas maliit na numero, pagkatapos ay ang may mas malaking numero, ang paikot na paghihintay (kondisyon ng Circular Wait) ay imposible. Sa malalaking proyekto, ang pagkakasunud-sunod ay itinatakda sa dokumentasyon at nabe-verify sa pamamagitan ng code review.
TryLock — paraan ng pag-lock na hindi humaharang sa thread nang walang katapusan, ngunit nagbabalik ng false kung ang lock ay hindi nakuha sa loob ng itinakdang oras. Sa Java, ito ay ipinatutupad sa pamamagitan ng ReentrantLock.tryLock(timeout, TimeUnit), sa Kotlin Coroutines — sa pamamagitan ng Mutex.withLock na may timeout. Kung nabigo, pinapalaya ng thread ang lahat ng nakuhang mapagkukunan at muling sumusubok mamaya.
Algorithm ng Banker — teoretikal na paraan ng pag-iwas sa Deadlock na iminungkahi ni Edsger Dijkstra. Ito ay nagmomodelo ng pamamahagi ng mapagkukunan bilang mga transaksyon sa bangko: ang sistema ay hindi naglalaan ng mapagkukunan kung ito ay maaaring humantong sa isang hindi ligtas na estado (deadlock). Sa pagsasagawa, ang algorithm ay bihirang inilalapat sa mobile development dahil sa pagiging kumplikado ng paunang kaalaman sa maximum na pangangailangan ng mga thread, ngunit ang mga prinsipyo nito ay ginagamit sa SQLite database at file system.
Mga Madalas Itanong
Hindi, para sa mutual blocking ay kailangan ng hindi bababa sa dalawang thread. Sa single-thread code, lahat ng operasyon ay isinasagawa nang sunud-sunod, kaya ang paikot na paghihintay ay imposible. Gayunpaman, ang Deadlock ay maaaring mangyari sa pagitan ng mga proseso kapag gumagamit ng file lock o inter-process semaphore.
Sa mga coroutine, ang Deadlock ay nangyayari sa antas ng suspend function at hindi hinaharangan ang OS thread, na ginagawang hindi gaanong kapansin-pansin. Ang Mutex mula sa kotlinx.coroutines ay suspending, hindi nito hinaharangan ang thread, ngunit ang coroutine ay hindi naisasagawa. Para sa pagtuklas, gamitin ang DebugProbes mula sa modyul na kotlinx-coroutines-debug.
SQLite Deadlock ay nangyayari kapag ang dalawang koneksyon sa database ay sumusubok na magsagawa ng mga transaksyon sa magkaibang pagkakasunud-sunod. Natutukoy ng SQLite ang mga ganitong sitwasyon at nagbabalik ng error code na SQLITE_BUSY o SQLITE_LOCKED. Sa Android, inirerekomenda ang paggamit ng Room na may iisang instance ng database at mga transaksyon sa pamamagitan ng @Transaction, na nag-aalis ng inter-connection Deadlock.
Android Runtime ay may built-in na Deadlock detector na naa-activate sa pagbuo ng ANR (Application Not Responding). Sinusuri ng system ang Thread Dump ng lahat ng thread ng application at minamarkahan ang mutual blocking. Ang resulta ay magagamit sa /data/anr/traces.txt at Google Play Console sa seksyong ANR Reports.
Una, kunin ang Thread Dump ng lahat ng thread ng application. Suriin kung anong mga lock ang hawak ng bawat thread at kung alin ang sinusubukan nitong makuha. Magpatupad ng Watchdog timer na may awtomatikong dump kapag lumampas sa limitasyon ng oras. Pagkatapos ng pag-aayos, magdagdag ng lint rule na ThreadSafety sa CI pipeline upang maiwasan ang pag-ulit.
Buod
Gagawa kami ng mobile application na turnkey
Gumagawa ang IT Sectr ng mga iOS at Android application para sa mga startup at negosyo mula noong 2017. Magpapayo kami sa iyo at magmumungkahi ng pinakamahusay na solusyon.
Basahin din