Lock: nedir, kilit türleri ve senkronizasyonda kullanımı

Yazar: IT Sectr Yayınlanma: 2026-03-19 Okuma süresi: 8 dk

Lock, çok iş parçacıklı uygulamalarda kodun kritik bölümlerine özel erişim sağlayan bir senkronizasyon mekanizmasıdır. Oracle, 2024'e göre, Lock arayüzü geleneksel synchronized bloklarına kıyasla daha esnek senkronizasyon kontrolü sağlar; zaman aşımı ile edinme denemeleri ve birden çok bekleme kuyruğu desteğini içerir.

Önemli Noktalar

  • Lock, Java'da açık kilit yönetimi için bir arayüzdür.
  • ReentrantLock, aynı iş parçacığı tarafından yeniden edinmeyi destekleyen temel bir uygulamadır.
  • ReadWriteLock, performansı artırmak için okuma ve yazma kilitlerini ayırır.
  • Deadlock, birden çok kilidi aynı anda kullanırken ana risktir.
  • synchronized'dan farklı olarak, Lock zaman aşımı ve kesilebilir bekleme desteği sunar.

Lock nedir?

Lock, veri erişimini senkronize etmek için açık kilitleme ve kilit açma işlemleri sağlayan java.util.concurrent.locks paketindeki bir arayüzdür. synchronized'dan farklı olarak Lock, geliştiriciye kilitleme mekanizması üzerinde tam kontrol sağlar.

Tanım ve senkronizasyondaki rolü

Lock arayüzü, Java 5'te yerleşik synchronized mekanizmasına bir alternatif olarak tanıtıldı. Ana yöntemler lock, unlock, tryLock ve lockInterruptibly'dir. Kilitler, çok iş parçacıklı bir ortamda güvenli veri erişimi düzenlemeye yardımcı olur ve yarış koşullarını ve veri bozulmasını önler.

Lock'un synchronized'a göre ana avantajı esnekliktir. Geliştirici, zaman aşımı ile bir kilidi edinmeyi deneyebilir, bloke olmadan kullanılabilirliğini kontrol edebilir veya farklı önceliklerle birden çok bekleme kuyruğu düzenleyebilir.

Tarihçe ve gelişim

Lock arayüzü Java 5'te ortaya çıkmadan önce, tek senkronizasyon yöntemi synchronized idi ve zaman aşımı olmaması, kesilebilir bekleme olmaması ve tek kuyruk gibi sınırlamalardan muzdaripti. Doug Lea, java.util.concurrent paketini tasarladı ve Lock'u temel bir yapı taşı olarak dahil etti.

Kilit nasıl çalışır?

Bir kilit, dahili bir durum bayrağı ve bekleme kuyruğu aracılığıyla erişimi yönetir. Bir iş parçacığı lock() çağırdığında, mekanizma kilidin boş olup olmadığını kontrol eder ve ya onu edinir ya da serbest bırakılana kadar iş parçacığını kuyruğa alır.

Atomik edinme ve serbest bırakma

Herhangi bir kilidin merkezinde bir atomik karşılaştır-ve-değiştir (CAS) işlemi bulunur. lock() çağrıldığında, iş parçacığı atomik olarak meşgul bayrağını ayarlamaya çalışır. Bayrak zaten ayarlanmışsa, iş parçacığı bloke olur. unlock() ile bayrak temizlenir ve bekleyen bir iş parçacığı uyandırılır.

kotlin
import java.util.concurrent.locks.ReentrantLock

val lock = ReentrantLock()

fun performTask() {
    lock.lock()
    try {
        // kritik bölüm
        println("${Thread.currentThread().name} iş parçacığı çalışıyor")
    } finally {
        lock.unlock()
    }
}

Bekleme kuyruğu ve uyandırma

ReentrantLock dahili olarak her bekleyen iş parçacığının bir düğümle temsil edildiği çift yönlü bağlı bir liste (CLH kilit kuyruğu) kullanır. Kilit serbest bırakıldığında, kuyruğun baş düğümü uyandırılır. Adil mod (fair) FIFO sırasını garanti ederken, adil olmayan mod verimi artırmak için yeni bir iş parçacığının bekleyenlerden önce kilidi edinmesine izin verir.

Başlıca kilit türleri

Modern Java ekosisteminde, her biri belirli senaryolar için optimize edilmiş birden çok kilit uygulaması bulunur. Doğru kilidi seçmek, çok iş parçacıklı bir uygulamanın performansını ve güvenilirliğini doğrudan etkiler.

ReentrantLock

ReentrantLock, Lock'un temel ve en çok kullanılan uygulamasıdır. Aynı iş parçacığı tarafından yeniden edinmeyi destekler: bir iş parçacığı kilidi zaten tutuyorsa, lock()'u tekrar çağırmak onu bloke etmez. Bu, özyinelemeli çağrılarda deadlock'u önler.

ReentrantReadWriteLock

ReadWriteLock, kilitleri iki moda ayırır: okuma ve yazma. Birden çok iş parçacığı aynı anda okuma kilidini tutabilir, ancak yazma özel erişim gerektirir. Bu, sık okuma ve seyrek yazma durumlarında performansı önemli ölçüde artırır.

StampedLock

StampedLock, Java 8'de tanıtılan en yeni uygulamadır. Üç modu destekler: yazma, okuma ve iyimser okuma. İyimser okuma diğer iş parçacıklarını bloke etmez ve okumadan sonra verileri doğrular, ReadWriteLock'a göre %10-20 performans avantajı sağlar.

KilitJava SürümüModlarPerformans
ReentrantLockJava 5özelyüksek
ReadWriteLockJava 5okuma + yazmaorta
StampedLockJava 8okuma + yazma + iyimserçok yüksek

ReentrantLock ve özellikleri

ReentrantLock, synchronized'da bulunmayan birçok özellik sunan en popüler Lock uygulamasıdır. Özelliklerini anlamak, etkili çoklu iş parçacığı çalışması için gereklidir.

Kilit adilliği (fairness)

ReentrantLock yapıcısı bir fair parametresi alır. true olduğunda, kilit FIFO sırasını garanti eder; false olduğunda, yeni bir iş parçacığı bekleyenlerden önce kilidi edinebilir. Adil mod açlığı önler ancak kuyruk bakımının ek yükü nedeniyle verimi %10-20 azaltır.

Zaman aşımı ve kesilebilir bekleme

synchronized'dan farklı olarak, ReentrantLock zaman aşımı ile tryLock'u destekler. Kilit belirtilen süre içinde edinilemezse, iş parçacığı süresiz olarak bloke olmak yerine yürütmeye devam eder. lockInterruptibly yöntemi, Thread.interrupt() aracılığıyla bekleyen bir iş parçacığını kesmeye izin verir.

kotlin
val lock = ReentrantLock()

fun tryTask() {
    if (lock.tryLock(500, TimeUnit.MILLISECONDS)) {
        try {
            println("Kilit edinildi")
        } finally {
            lock.unlock()
        }
    } else {
        println("Kilit edinilemedi")
    }
}

Koşullar (Conditions)

ReentrantLock, newCondition() yöntemi aracılığıyla birden çok koşul değişkenini destekler. Her Condition'ın kendi bekleme kuyruğu vardır ve karmaşık uyandırma senaryolarına olanak tanır. await() ve signal() yöntemleri, synchronized bloklarındaki wait() ve notify()'un yerini almıştır, ancak birden çok kuyruk desteğiyle.

ReadWriteLock ve StampedLock

ReadWriteLock ve StampedLock, okumaların yazmalara göre daha baskın olduğu durumlarda erişim optimizasyonunu ele alır. Okumanın yazmadan daha sık gerçekleştiği senaryolarda ReentrantLock'tan önemli ölçüde daha verimlidirler.

ReadWriteLock pratikte

ReadWriteLock arayüzü iki yöntem içerir: readLock() ve writeLock(). Okuma kilidi birden çok iş parçacığı tarafından aynı anda tutulabilirken, yazma kilidi özeldir. Tipik bir örnek, iş parçacığı güvenli bir önbellektir: birçok iş parçacığı verileri okurken yalnızca biri periyodik olarak günceller.

kotlin
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 ve iyimser okuma

StampedLock üçüncü bir mod ekler — tryOptimisticRead. Bu mod diğer iş parçacıklarını bloke etmez, yalnızca bir durum damgası (stamp) kaydeder. Okumadan sonra, geliştirici okuma sırasında verilerin değişip değişmediğini kontrol etmek için validate(stamp) çağırır. Veriler değişmişse, işlem tekrarlanmalıdır.

Mobil geliştirmede kilitler

Mobil uygulamalarda, kilitler iş parçacıkları arasında paylaşılan verilere erişimi koordine etmek için kullanılır. Ancak, sınırlı cihaz kaynakları ve UI yanıt verebilirliğini sürdürme ihtiyacı nedeniyle kullanımları özel dikkat gerektirir.

Android'de (Kotlin) kilitler

Android'de ReentrantLock, Room, önbellekler ve dosyalarla çalışırken kullanışlıdır. Hatırlanması önemli: ana iş parçacığında asla kilit edinmeyin. Zaman uyumsuz kod için, kotlinx.coroutines'den coroutine'ler ve Mutex tercih edilir; bunlar iş parçacığını bloke etmek yerine coroutine'i askıya alır.

iOS'te (Swift) kilitler

iOS'te standart NSLock daha az kullanılır — geliştiriciler bariyer bayraklarıyla DispatchQueue veya os_unfair_lock'u tercih eder. Swift 5.7+, durumu otomatik olarak koruyan aktörler aracılığıyla modern senkronizasyon mekanizmaları sağlar.

swift
import Foundation

actor DataStore {
    private var items: [String] = []

    func add(_ item: String) {
        items.append(item)
    }

    func getAll() -> [String] {
        items
    }
}

Deadlock önleme önerileri

Deadlock'ları önlemek için proje genelinde tutarlı bir kilit sırası izleyin. Uzun süreli bloke olmanın mümkün olduğu her yerde lock() yerine zaman aşımı ile tryLock kullanın. Geleneksel kilitler yerine Lock-Free algoritmaları (AtomicReference, ConcurrentHashMap) kullanmayı düşünün.

Lock ile çalışmak için en iyi uygulamalar

Lock kullanımı, deadlock'ları ve performans düşüşünü önleyen disiplin ve birkaç kurala uymayı gerektirir. Bu uygulamalar, Java topluluğu tarafından java.util.concurrent paketinin 20 yıllık kullanımı boyunca geliştirilmiştir.

Finally'de serbest bırakma

En önemli desen finally'de kilidi serbest bırakmaktır. Kritik bölüm başarıyla tamamlansın veya bir istisna atsın, kilit serbest bırakılmalıdır. Bu, diğer iş parçacıklarının tek bir hata nedeniyle sonsuza kadar bloke olmamasını sağlar. Kotlin'de bu desen, withLock uzantısı ile zarif bir şekilde çözülür.

Tutma süresini en aza indirme

Kritik bölüm mümkün olduğunca kısa olmalıdır. Bir kilit içinde asla G/Ç, ağ istekleri veya uzun hesaplamalar yapmayın. Bir sunucudan veri okumanız gerekiyorsa, önce verileri alın, ardından yalnızca paylaşılan durumu güncellemek için kilidi edinin. Bu, rekabeti azaltır ve sistem verimini artırır.

Tutarlı kilit sırası

Birden çok kilit ile çalışırken deadlock'ları önlemek için proje genelinde küresel bir kilit sırası oluşturun. Önce lockA, ardından lockB ediniliyorsa, herhangi bir ters sıra kod inceleme kuralları tarafından yasaklanmalıdır. Otomatik doğrulama için SpotBugs ve IntelliJ Inspections gibi statik analizörler kullanın.

Sıkça Sorulan Sorular

Lock ve synchronized arasındaki fark nedir?

Lock, zaman aşımı ve kesilebilir bekleme desteği olan açık bir arayüzdür. synchronized otomatik olarak monitörü edinir ve serbest bırakır, ancak tryLock, lockInterruptibly veya birden çok Condition kullanımına izin vermez. Lock daha esnektir ancak finally'de manuel serbest bırakma gerektirir.

Adil kilit (fair lock) nedir?

Adil kilit FIFO sırasını garanti eder: en uzun süredir bekleyen iş parçacığı kilidi ilk alır. Adil olmayan kilit, bekleyenlerden önce yeni bir iş parçacığına erişim verebilir, bu da verimi artırır ancak bekleyen iş parçacıklarının aç kalmasına neden olabilir.

Lock kullanırken deadlock'tan nasıl kaçınılır?

Tüm kilitleri edinmek için sabit bir sıra izleyin, koşulsuz lock() yerine zaman aşımı ile tryLock kullanın ve aynı anda tutulan kilit sayısını en aza indirin. Lock-Free veri yapılarının kullanımı da deadlock riskini azaltır.

Lock'ta Condition nedir?

Condition, Lock için wait/notify benzeridir ve birden çok bağımsız bekleme kuyruğuna izin verir. Her newCondition() çağrısı ayrı bir kuyruk oluşturur ve synchronized'ın tek kuyruğuna kıyasla iş parçacığı uyandırma üzerinde daha hassas kontrol sağlar.

Mobil uygulama için hangi Lock seçilmelidir?

Coroutine'ler ile Android için kotlinx.coroutines'den Mutex kullanın — iş parçacığını bloke etmek yerine coroutine'i askıya alır. Swift 5.7+ ile iOS için, durum erişimini otomatik olarak senkronize eden aktörler tercih edilir. ReentrantLock'u eski kod ve düşük seviyeli senaryolar için saklayın.

Özet

  • Lock, java.util.concurrent.locks'dan açık bir kilit yönetim arayüzüdür.
  • ReentrantLock, yeniden edinme ve adilliği destekleyen ana uygulamadır.
  • ReadWriteLock, okuma ağırlıklı senaryolar için okuma ve yazma kilitlerini ayırır.
  • StampedLock, maksimum performans için iyimser okuma ekler.
  • Zaman aşımları ve Condition'lar, Lock'un synchronized'a göre temel avantajlarıdır.
  • Deadlock, tutarlı kilit sırası ve tryLock kullanımı ile önlenir.
  • Mobil geliştirmede, coroutine'ler (Android) ve aktörler (iOS) önerilir.

Anahtar teslim bir mobil uygulama geliştireceğiz

IT Sectr, 2017'den beri girişimler ve işletmeler için iOS ve Android uygulamaları oluşturmaktadır. Size danışmanlık yapacak ve en iyi çözümü önereceğiz.

Projeyi tartış

Ayrıca okuyun