Deadlock (saling blokir) — adalah keadaan di mana dua atau lebih utas tanpa henti menunggu pembebasan sumber daya yang diambil oleh peserta lain. Menurut Oracle Java Tutorials (2024), Deadlock terjadi pada penantian melingkar, ketika setiap utas memegang kunci yang diperlukan oleh utas lain. Tanpa alat deteksi khusus, Deadlock sepenuhnya menghentikan eksekusi aplikasi tanpa kesalahan yang terlihat.
Poin Utama
Deadlock (saling blokir) — adalah situasi dalam pemrograman multi-utas di mana dua atau lebih utas saling memblokir secara permanen. Setiap utas memegang sumber daya yang diperlukan oleh utas lain dan tidak melepaskannya, menunggu pengambilan sumber daya yang hilang. Akibatnya, tidak satupun utas dapat melanjutkan eksekusi.
Dalam pengembangan seluler, Deadlock sangat kritis karena tidak menyebabkan pengecualian atau crash. Aplikasi hanya berhenti merespons tindakan pengguna (ANR — Application Not Responding), dan satu-satunya jalan keluar adalah penghentian paksa proses. Menurut data Google (Android Performance Patterns, 2023), sekitar 15% laporan ANR di Google Play Console terkait dengan saling blokir di utas latar belakang.
Perbedaan utama Deadlock dari masalah konkurensi lainnya — ketidakmampuan pemulihan tanpa intervensi eksternal. Utas tidak akan melepaskan sumber daya sendiri, karena penjadwal sistem operasi tidak dapat mencabut kunci secara paksa. Ini membedakan Deadlock dari Livelock, di mana utas aktif tetapi tidak melakukan pekerjaan yang berguna.
Pada tahun 1971, Edward G. Coffman merumuskan empat kondisi wajib yang diperlukan untuk terjadinya Deadlock. Jika setidaknya satu dari kondisi tersebut tidak ada, saling blokir tidak mungkin terjadi. Kondisi ini dikenal sebagai kondisi Coffman dan mendasari semua algoritma pencegahan Deadlock.
Sumber daya dapat diambil hanya oleh satu utas pada satu waktu. Jika sumber daya memungkinkan pembacaan simultan oleh beberapa utas (misalnya, ReadWriteLock dalam mode baca), Deadlock tidak terjadi. Kondisi ini berasal dari sifat alami Mutex dan kunci.
Sebuah utas menahan sumber daya yang sudah diambil dan pada saat yang sama menunggu pengambilan sumber daya lain. Jika utas dapat melepaskan sumber daya saat ini sebelum meminta sumber daya berikutnya (melalui penguncian dua fase), kondisi Hold and Wait dilanggar. Di Android, ini sering muncul ketika utas memegang kunci basis data dan mencoba mengambil kunci SharedPreferences.
Sistem operasi tidak dapat secara paksa mengambil kunci dari utas. Sumber daya hanya dilepaskan ketika utas sendiri melepaskannya. Di beberapa sistem (misalnya, mode WAL SQLite), paksaan diterapkan pada tingkat operasi individu, yang mengurangi risiko Deadlock.
Ada rantai utas tertutup, di mana masing-masing menunggu sumber daya yang dipegang oleh utas berikutnya dalam rantai. Misalnya, utas A memegang sumber daya 1 dan menunggu sumber daya 2, utas B memegang sumber daya 2 dan menunggu sumber daya 1. Ini adalah satu-satunya kondisi yang dapat dihilangkan oleh pengembang secara arsitektural — melalui hierarki kunci. Jika semua utas mengambil sumber daya dalam urutan global yang ditentukan secara ketat, siklus secara fisik tidak mungkin terjadi.
Dalam praktiknya, di aplikasi Android, Deadlock paling sering terjadi karena persilangan implisit kunci dari berbagai tingkat: kunci basis data (Room), kunci SharedPreferences, dan kunci koleksi di memori. Masing-masing kunci ini dikelola oleh komponen yang berbeda, dan tanpa protokol terpusat untuk urutan pengambilan, pengembang secara tidak sengaja membuat siklus.
Mari kita lihat contoh klasik saling blokir — dua utas mengambil kunci dalam urutan yang berbeda. Jika utas pertama memblokir sumber daya A dan mencoba mengambil B, dan utas kedua memblokir B dan mencoba mengambil A, maka Deadlock terjadi.
class DeadlockExample {
private val lockA = Any()
private val lockB = Any()
fun operationA() {
synchronized(lockA) {
Thread.sleep(50) // simulasi kerja
synchronized(lockB) {
println("operationA selesai")
}
}
}
fun operationB() {
synchronized(lockB) { // urutan terbalik!
Thread.sleep(50)
synchronized(lockA) {
println("operationB selesai")
}
}
}
}
fun main() {
val ex = DeadlockExample()
Thread { ex.operationA() }.start()
Thread { ex.operationB() }.start()
// Aplikasi akan macet selamanya — Deadlock!
}
Dalam contoh ini, operationA mengambil lockA, dan operationB mengambil lockB. Kemudian masing-masing mencoba mengambil kunci kedua — dan keduanya menunggu tanpa henti. Program macet tanpa pesan kesalahan. Satu-satunya cara perbaikan adalah menjamin urutan pengambilan kunci yang sama di semua metode.
Ketiga masalah konkurensi ini sering membingungkan, tetapi mekanisme dan konsekuensinya pada dasarnya berbeda. Deadlock — penghentian total, Starvation — penantian tanpa henti untuk sumber daya, Livelock — tidak aktif secara aktif. Memahami perbedaan sangat penting untuk memilih strategi eliminasi yang tepat.
| Karakteristik | Deadlock | Starvation | Livelock |
|---|---|---|---|
| Status utas | Diblokir (BLOCKED) | Siap (RUNNABLE) | Aktif (RUNNABLE) |
| Eksekusi pekerjaan | Tidak | Tidak | Ya, tapi tidak berguna |
| Penyebab | Penantian melingkar | Penjadwalan tidak adil | Penanganan konflik yang salah |
| Deteksi | Thread Dump, batas waktu | Pemantauan kemajuan | Penghitung percobaan ulang |
Starvation (kelaparan) terjadi ketika penjadwal terus-menerus menunda eksekusi utas berprioritas rendah demi utas lain. Tidak seperti Deadlock, utas tidak diblokir — ia siap untuk dieksekusi tetapi tidak mendapatkan waktu CPU. Di Android, skenario tipikal adalah utas latar belakang dengan prioritas rendah yang tidak pernah dieksekusi jika utas UI dan Service terus-menerus aktif.
Livelock (kunci aktif) — situasi di mana utas tidak diblokir, tetapi tanpa henti bereaksi terhadap tindakan satu sama lain, tanpa melakukan pekerjaan yang berguna. Analogi klasik — dua orang bertemu di koridor dan keduanya mencoba memberi jalan, bergerak ke arah yang sama. Tidak seperti Deadlock, utas dalam Livelock menghabiskan CPU, menguras baterai perangkat.
Thread Dump (dump utas) — alat utama untuk mendeteksi saling blokir di JVM dan Android Runtime. Saat dump, JVM secara otomatis menganalisis grafik ketergantungan antara monitor dan menandai siklus Deadlock. Di Android Studio, dump utas dapat diperoleh melalui Android Profiler atau perintah kill -3 PID dari ADB Shell.
Deteksi otomatis Deadlock saat eksekusi dilakukan melalui pengatur waktu Watchdog. Jika utas tidak menyelesaikan operasi dalam batas waktu yang ditentukan, watchdog memulai pembuatan dump dan mengirim laporan ke sistem pelaporan crash (Firebase Crashlytics, Sentry). Menurut data Sentry (Issue Resolution Report, 2024), konfigurasi watchdog mengurangi waktu diagnosis Deadlock dari minggu menjadi beberapa jam.
Pada tahap pengembangan, penganalisis statis ThreadSafe dari JetBrains dan Checker Framework dengan modul Lock Checker efektif. Alat-alat ini menganalisis urutan pengambilan kunci pada tingkat kode sumber dan memperingatkan tentang potensi siklus. Selain itu, direkomendasikan Test-Driven Deadlock Detection — tes stres yang menjalankan operasi dengan urutan penguncian berbeda dalam ratusan utas.
Perhatian khusus layak diberikan pada Cooperative Deadlock Detection — metode di mana utas bertukar informasi tentang kunci yang diambil melalui registri global. Jika utas mendeteksi potensi siklus, ia melepaskan semua sumber daya dan mengulangi operasi. Pendekatan ini digunakan dalam sistem terdistribusi (Apache ZooKeeper, Google Chubby) dan secara bertahap diperkenalkan dalam pengembangan seluler melalui pustaka seperti Jetpack Sync.
Cara paling andal — menetapkan urutan global pengambilan kunci di seluruh aplikasi. Jika semua utas selalu mengambil kunci dengan nomor lebih kecil terlebih dahulu, kemudian dengan nomor lebih besar, penantian melingkar (kondisi Circular Wait) tidak mungkin terjadi. Dalam proyek besar, urutan ditetapkan dalam dokumentasi dan diverifikasi melalui tinjauan kode.
TryLock — metode penguncian yang tidak memblokir utas tanpa henti, tetapi mengembalikan false jika kunci tidak diperoleh dalam waktu tertentu. Di Java, ini diimplementasikan melalui ReentrantLock.tryLock(timeout, TimeUnit), di Kotlin Coroutines — melalui Mutex.withLock dengan batas waktu. Jika gagal, utas melepaskan semua sumber daya yang diambil dan mencoba lagi nanti.
Algoritma Bankir — metode teoritis pencegahan Deadlock yang diusulkan oleh Edsger Dijkstra. Ini memodelkan distribusi sumber daya sebagai transaksi bank: sistem tidak mengalokasikan sumber daya jika ini dapat menyebabkan keadaan tidak aman (deadlock). Dalam praktiknya, algoritma ini jarang diterapkan dalam pengembangan seluler karena rumitnya mengetahui terlebih dahulu kebutuhan maksimum utas, tetapi prinsipnya digunakan dalam basis data SQLite dan sistem file.
Pertanyaan yang Sering Diajukan
Tidak, untuk saling blokir diperlukan setidaknya dua utas. Dalam kode utas tunggal, semua operasi dilakukan secara berurutan, sehingga penantian melingkar tidak mungkin terjadi. Namun, Deadlock dapat terjadi antar proses saat menggunakan kunci file atau semafor antar proses.
Di korutin, Deadlock terjadi pada tingkat fungsi yang ditangguhkan (suspend) dan tidak memblokir utas OS, sehingga kurang terlihat. Mutex dari kotlinx.coroutines bersifat menangguhkan (suspending), tidak memblokir utas, tetapi korutin tidak dijalankan. Untuk deteksi, gunakan DebugProbes dari modul kotlinx-coroutines-debug.
Deadlock SQLite terjadi ketika dua koneksi basis data mencoba melakukan transaksi dalam urutan yang berbeda. SQLite mendeteksi situasi seperti itu dan mengembalikan kode kesalahan SQLITE_BUSY atau SQLITE_LOCKED. Di Android, direkomendasikan menggunakan Room dengan satu instance basis data dan transaksi melalui @Transaction, yang menghilangkan Deadlock antar koneksi.
Android Runtime memiliki detektor Deadlock bawaan yang diaktifkan saat menghasilkan ANR (Application Not Responding). Sistem menganalisis Thread Dump semua utas aplikasi dan menandai saling blokir. Hasilnya tersedia di /data/anr/traces.txt dan Google Play Console di bagian ANR Reports.
Pertama, dapatkan Thread Dump semua utas aplikasi. Analisis kunci apa yang dipegang setiap utas dan kunci apa yang coba diambil. Terapkan pengatur waktu Watchdog dengan dump otomatis saat batas waktu terlampaui. Setelah perbaikan, tambahkan aturan lint ThreadSafety di pipeline CI untuk mencegah kekambuhan.
Kesimpulan
Kami akan mengembangkan aplikasi seluler turnkey
IT Sectr membuat aplikasi iOS dan Android untuk startup dan bisnis sejak 2017. Kami akan memberi saran dan mengusulkan solusi terbaik.
Baca juga