Lock adalah mekanisme sinkronisasi yang menyediakan akses eksklusif ke bagian kritis kode dalam aplikasi multithreading. Menurut Oracle, 2024, antarmuka Lock memberikan kontrol sinkronisasi yang lebih fleksibel dibandingkan blok synchronized tradisional, termasuk percobaan penguncian dengan timeout dan dukungan untuk beberapa antrian tunggu.
Poin Utama
Lock adalah antarmuka dari paket java.util.concurrent.locks yang menyediakan operasi penguncian dan pelepasan eksplisit untuk sinkronisasi akses data. Berbeda dengan synchronized, Lock memberikan pengembang kendali penuh atas mekanisme penguncian.
Antarmuka Lock muncul di Java 5 sebagai alternatif untuk mekanisme synchronized bawaan. Metode utamanya adalah lock, unlock, tryLock dan lockInterruptibly. Penguncian memungkinkan pengorganisasian akses aman ke data di lingkungan multithreading, mencegah kondisi balapan dan kerusakan data.
Keuntungan utama Lock dibandingkan synchronized adalah fleksibilitas. Pengembang dapat mencoba mengunci dengan timeout, memeriksa status penguncian tanpa memblokir, atau mengatur beberapa antrian tunggu dengan prioritas berbeda.
Sebelum munculnya antarmuka Lock di Java 5, satu-satunya cara sinkronisasi adalah synchronized, yang menderita keterbatasan: kurangnya timeout, ketidakmampuan menginterupsi penantian, dan antrian tunggal. Doug Lea merancang paket java.util.concurrent, menyertakan Lock sebagai blok bangunan fundamental.
Penguncian mengelola akses melalui bendera status internal dan antrian tunggu. Ketika sebuah thread memanggil lock(), mekanisme memeriksa apakah penguncian bebas dan menguncinya atau menempatkan thread dalam antrian hingga dilepaskan.
Di dasar setiap penguncian terdapat operasi atomik perbandingan dan pengaturan (CAS). Saat memanggil lock(), thread mencoba mengatur bendera kesibukan secara atomik. Jika bendera sudah diatur, thread akan diblokir. Saat unlock(), bendera direset dan salah satu thread yang menunggu dibangunkan.
import java.util.concurrent.locks.ReentrantLock
val lock = ReentrantLock()
fun performTask() {
lock.lock()
try {
// bagian kritis
println("Thread ${Thread.currentThread().name} bekerja")
} finally {
lock.unlock()
}
}
ReentrantLock secara internal menggunakan antrian dua arah (CLH lock queue), di mana setiap thread yang menunggu diwakili oleh simpul. Ketika penguncian dilepaskan, simpul kepala antrian dibangunkan. Mode fair (adil) menjamin urutan FIFO, sedangkan unfair memungkinkan penguncian oleh thread baru sebelum yang menunggu untuk meningkatkan throughput.
Dalam tumpukan Java modern ada beberapa implementasi penguncian, masing-masing dioptimalkan untuk skenario tertentu. Pemilihan penguncian yang tepat secara langsung mempengaruhi kinerja dan keandalan aplikasi multithreading.
ReentrantLock — implementasi dasar dan paling sering digunakan dari Lock. Ini mendukung penguncian ulang oleh thread yang sama: jika thread sudah memiliki penguncian, panggilan lock() berulang tidak memblokirnya. Ini mencegah deadlock pada panggilan rekursif.
ReadWriteLock memisahkan penguncian menjadi dua mode: baca dan tulis. Beberapa thread dapat secara bersamaan memegang penguncian baca, tetapi menulis memerlukan akses eksklusif. Ini secara signifikan meningkatkan kinerja saat pembacaan sering dan penulisan jarang.
StampedLock — implementasi terbaru, yang muncul di Java 8. Ini mendukung tiga mode: tulis, baca dan baca optimistis. Baca optimistis tidak memblokir thread lain dan memvalidasi data setelah pembacaan, memberikan peningkatan kinerja 10-20% dibandingkan ReadWriteLock.
| Penguncian | Versi Java | Mode | Kinerja |
|---|---|---|---|
| ReentrantLock | Java 5 | eksklusif | tinggi |
| ReadWriteLock | Java 5 | baca + tulis | sedang |
| StampedLock | Java 8 | baca + tulis + optimistic | sangat tinggi |
ReentrantLock — implementasi Lock yang paling populer, menyediakan sejumlah kemampuan yang tidak tersedia di synchronized. Memahami fitur-fiturnya diperlukan untuk bekerja efektif dengan multithreading.
Konstruktor ReentrantLock menerima parameter fair. Saat true, penguncian menjamin urutan FIFO akses, saat false, penguncian oleh thread baru sebelum yang menunggu dimungkinkan. Mode adil mencegah kelaparan, tetapi mengurangi throughput sebesar 10-20% karena overhead tambahan untuk memelihara antrian.
Berbeda dengan synchronized, ReentrantLock mendukung tryLock dengan timeout. Jika penguncian tidak dapat diperoleh dalam waktu yang ditentukan, thread melanjutkan eksekusi alih-alih memblokir tanpa batas. Metode lockInterruptibly memungkinkan menginterupsi thread yang menunggu melalui Thread.interrupt().
val lock = ReentrantLock()
fun tryTask() {
if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
try {
println("Penguncian diperoleh")
} finally {
lock.unlock()
}
} else {
println("Gagal memperoleh penguncian")
}
}
ReentrantLock mendukung beberapa variabel kondisi melalui metode newCondition(). Setiap Condition memiliki antrian tunggunya sendiri, memungkinkan pengorganisasian skenario pembangunan yang kompleks. Metode await() dan signal() menggantikan wait() dan notify() dari blok synchronized, tetapi dengan dukungan untuk beberapa antrian.
ReadWriteLock dan StampedLock memecahkan masalah optimasi akses ketika operasi baca mendominasi tulis. Mereka secara signifikan lebih efisien daripada ReentrantLock dalam skenario di mana pembacaan lebih sering terjadi daripada penulisan.
Antarmuka ReadWriteLock berisi dua metode: readLock() dan writeLock(). Penguncian baca dapat dipegang oleh beberapa thread secara bersamaan, penguncian tulis — hanya oleh satu. Contoh tipikal — cache thread-safe: banyak thread membaca data, dan hanya satu yang secara periodik memperbaruinya.
class SafeCache<K, V> {
private val map = mutableMapOf<K, V>()
private val rwLock = ReentrantReadWriteLock()
fun get(key: K): V? {
rwLock.readLock().lock()
return try { map[key] } finally { rwLock.readLock().unlock() }
}
fun put(key: K, value: V) {
rwLock.writeLock().lock()
return try { map[key] = value } finally { rwLock.writeLock().unlock() }
}
}
StampedLock menambahkan mode ketiga — tryOptimisticRead. Mode ini tidak memblokir thread lain, hanya mengingat cap (stamp) status. Setelah pembacaan, pengembang memanggil validate(stamp) untuk memeriksa apakah data berubah selama pembacaan. Jika data berubah, operasi harus diulang.
Dalam aplikasi mobile, penguncian digunakan untuk koordinasi akses ke data bersama antar thread. Namun, penggunaannya memerlukan kehati-hatian khusus karena sumber daya perangkat yang terbatas dan kebutuhan untuk mempertahankan responsivitas antarmuka.
Di Android, ReentrantLock berguna saat bekerja dengan Room, cache dan file. Penting untuk diingat: jangan pernah mengunci di thread utama. Untuk kode asinkron, lebih baik menggunakan korutin dan Mutex dari kotlinx.coroutines, yang tidak memblokir thread melainkan menunda korutin.
Di iOS, Lock standar dari NSLock lebih jarang digunakan — pengembang lebih memilih DispatchQueue dengan bendera barrier atau penguncian operasional os_unfair_lock. Swift 5.7+ menyediakan mekanisme sinkronisasi modern melalui actors, yang secara otomatis melindungi status.
import Foundation
actor DataStore {
private var items: [String] = []
func add(_ item: String) {
items.append(item)
}
func getAll() -> [String] {
items
}
}
Untuk menghindari deadlock, patuhi urutan penguncian seragam dari semua penguncian dalam proyek. Gunakan tryLock dengan timeout alih-alih lock() di mana pun penguncian jangka panjang mungkin terjadi. Pertimbangkan penerapan algoritma Lock-Free (AtomicReference, ConcurrentHashMap) alih-alih penguncian tradisional.
Penggunaan Lock memerlukan disiplin dan kepatuhan pada beberapa aturan yang mencegah deadlock dan penurunan kinerja. Praktik-praktik ini dikembangkan oleh komunitas Java selama 20 tahun penggunaan paket java.util.concurrent.
Pola terpenting — lock di finally. Terlepas dari apakah bagian kritis berhasil atau dengan pengecualian, penguncian harus dilepaskan. Ini menjamin bahwa thread lain tidak akan terblokir selamanya karena satu kesalahan. Di Kotlin, pola ini diselesaikan dengan elegan melalui ekstensi withLock.
Bagian kritis harus maksimal pendek. Jangan pernah melakukan operasi input-output, permintaan jaringan atau perhitungan panjang di dalam penguncian. Jika perlu membaca data dari server, ambil dulu, lalu kunci hanya untuk memperbarui status bersama. Ini mengurangi persaingan dan meningkatkan throughput sistem.
Untuk mencegah deadlock saat bekerja dengan beberapa Lock, tetapkan urutan penguncian global di seluruh proyek. Jika pertama lockA, kemudian lockB — setiap urutan terbalik harus dilarang oleh aturan code review. Untuk pemeriksaan otomatis, gunakan penganalisis statis seperti SpotBugs dan IntelliJ Inspections.
Pertanyaan yang Sering Diajukan
Lock — antarmuka eksplisit dengan kemampuan timeout dan penantian dapat diinterupsi. synchronized secara otomatis mengunci dan melepas monitor, tetapi tidak mengizinkan penggunaan tryLock, lockInterruptibly dan beberapa Condition. Lock lebih fleksibel, tetapi memerlukan pelepasan manual di finally.
Penguncian adil menjamin urutan FIFO akses: thread yang menunggu paling lama mendapatkan penguncian pertama. Penguncian tidak adil dapat memberikan akses ke thread baru dengan melewati antrian, yang meningkatkan throughput tetapi dapat menyebabkan kelaparan thread yang menunggu.
Patuhi urutan tetap penguncian semua lock, gunakan tryLock dengan timeout alih-alih lock tanpa syarat, dan minimalkan jumlah penguncian yang dipegang bersamaan. Penerapan struktur data Lock-Free juga mengurangi risiko deadlock.
Condition — analog wait/notify untuk Lock, yang memungkinkan pengorganisasian beberapa antrian tunggu independen. Setiap panggilan newCondition() membuat antrian terpisah, memberikan kontrol yang lebih tepat atas pembangunan thread dibandingkan dengan antrian tunggal synchronized.
Untuk Android dengan korutin, gunakan Mutex dari kotlinx.coroutines — ini menunda korutin, bukan memblokir thread. Untuk iOS dengan Swift 5.7+, actors lebih disukai, yang secara otomatis menyinkronkan akses ke status. Biarkan ReentrantLock untuk kode lama dan skenario tingkat rendah.
Ringkasan
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