Synchronized, Java dilinde yerleşik bir senkronizasyon mekanizmasıdır ve kritik kod bölümlerine özel erişim sağlar. Oracle, 2024'e göre, synchronized değiştiricisi, işaretlenen metot veya bloğun belirli bir zamanda yalnızca bir iş parçacığı tarafından yürütülebilmesini garanti eder. Bu mekanizma, işletim sistemlerinin temel bir kavramı olan monitörlere dayanır ve her karmaşıklık seviyesindeki çok iş parçacıklı uygulamaların doğru çalışmasını sağlar.
Önemli Noktalar
Synchronized, Java'da aynı anda yalnızca bir iş parçacığının korunan bir kod bölümünü yürütmesini garanti eden ve eşzamanlı erişim sırasında veri bozulmasını önleyen bir anahtar kelimedir. Java'nın ilk sürümünde ortaya çıkmıştır ve her seviyedeki geliştirici için iş parçacığı güvenliği sağlamanın en basit yolu olmaya devam etmektedir.
synchronized değiştiricisi iki görevi çözer: karşılıklı dışlama (mutual exclusion) ve değişikliklerin görünürlüğü (visibility). Bir iş parçacığı synchronized bloktan çıktığında, tüm değişiklikler aynı nesne üzerinde senkronize edilmiş bir bloğa giren diğer iş parçacıklarına görünür olur.
Synchronized, bir metodun tamamına veya bir monitör nesnesi belirterek rastgele bir kod bloğuna uygulanabilir. Her iki durumda da JVM, bayt kodu düzeyinde monitorenter ve monitorexit talimatlarını ekler.
Senkronizasyon olmadan çok iş parçacıklı uygulamalarda, iki iş parçacığı aynı anda aynı veriyi değiştirdiğinde yarış durumu (race condition) oluşur ve öngörülemeyen sonuçlara yol açar. Synchronized, bu sorunla mücadele etmek için Java'nın ilk ve ana aracı haline gelmiş, her geliştiricinin erişebileceği basit bir bildirim sözdizimi sağlamıştır.
Synchronized mekanizması, her Java nesnesine yerleşik yüksek seviyeli bir senkronizyon ilkel öğesi olan monitör kavramına dayanır. Monitör, üzerinde ilk kez synchronized blok kullanıldığında bir nesneyle ilişkilendirilir.
Java'daki her nesnenin ilişkili bir monitörü vardır. Bir iş parçacığı synchronized bloğa girdiğinde, nesnenin monitörünü alır. Monitör zaten başka bir iş parçacığı tarafından tutuluyorsa, iş parçacığı serbest kalana kadar bloke olur. Bayt kodunda bu, monitorenter ve monitorexit talimat çiftine karşılık gelir.
JVM, synchronized'ı birkaç seviyede optimize eder: tek iş parçacıklı erişim için biased locking (yanlı kilitleme), düşük rekabet için lightweight locking (hafif kilitleme) ve işletim sistemi katılımıyla yoğun rekabet için heavyweight locking (ağır kilitleme). Bu seviyeler, kodu değiştirmeden performansı artırır.
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Synchronized bir happens-before ilişkisi kurar: synchronized bloktan çıkmadan önce bir iş parçacığındaki tüm eylemler, aynı nesne üzerinde senkronize edilmiş bir bloğa girdikten sonra başka bir iş parçacığına görünür olur. Bu, yalnızca karşılıklı dışlamayı değil, tüm iş parçacıkları için veri tutarlılığını da garanti eder.
Java, synchronized'ı uygulamak için iki yol sunar: metot düzeyinde ve blok düzeyinde. Aralarındaki seçim, performansı ve senkronizasyon ayrıntı düzeyini etkiler.
Bir metodu synchronized değiştiricisiyle işaretlemek, onu otomatik olarak geçerli örnek (örnek metotları için) veya Class nesnesi (statik metotlar için) üzerinde senkronize eder. Bu, karşılıklı dışlamayı sağlamanın en basit yoludur, ancak kritik bölüm metodun yalnızca küçük bir parçasını oluşturuyorsa ve kodun geri kalanı senkronizasyon gerektirmiyorsa genellikle aşırıdır.
Synchronized blok hassas kontrol sağlar: monitör nesnesini belirtir ve kodun yalnızca gerekli bölümünü senkronize eder, metodun geri kalanını kilidin dışında bırakır. Bu, monitör tutma süresini en aza indirir ve çok iş parçacıklı bir ortamda uygulamanın genel performansını artırır, çünkü diğer iş parçacıkları monitörün serbest kalmasını beklemeden ilgisiz kodu paralel olarak yürütebilir.
class DataProcessor {
private final Object lock = new Object();
public void process() {
// kritik bölüm dışındaki kod - senkronizasyon olmadan
prepareData()
synchronized (lock) {
// yalnızca bu blok korunuyor
updateSharedState()
}
// kilit olmadan devam
cleanup()
}
}
| Kriter | Synchronized metot | Synchronized blok |
|---|---|---|
| Monitör | this (örnek) veya Class | herhangi bir nesne |
| Ayrıntı düzeyi | tüm metot | yalnızca gerekli kod |
| Okunabilirlik | yüksek | orta |
| Performans | büyük metotlarda düşük | küçük kritik bölümlerde yüksek |
Android geliştirmede, synchronized SharedPreferences, veritabanı erişimi ve UI bileşenlerini korumak için yaygın olarak kullanılır. Ancak, arayüz donması riski nedeniyle ana iş parçacığında kullanımı kesinlikle önerilmez.
Android'de SharedPreferences temel iş parçacığı güvenliği sağlar, ancak Editor aracılığıyla birden çok iş parçacığından düzenleme yaparken harici senkronizasyon gerekebilir. Ayrı bir kilit nesnesiyle synchronized blok, değişikliklerin tutarlılığını garanti eder.
class PreferencesManager(private val prefs: SharedPreferences) {
private val lock = Any()
fun writeToken(token: String) {
synchronized (lock) {
prefs.edit()
.putString("auth_token", token)
.apply()
}
}
}
Android'de synchronized'ın ana sınırlaması iş parçacığı bloklamasıdır. Mutex ile coroutine'lerin aksine, synchronized sistem iş parçacığını tamamen bloke eder. Ana iş parçacığında bu, ANR'ye neden olur. Modern Android geliştirmede, synchronized'ın coroutine'ler (suspend Mutex) veya atomik türler (AtomicInteger) ile değiştirilmesi önerilir.
Modern Java ve Kotlin, synchronized'a çeşitli alternatifler sunar; her biri aynı sorunları daha az sınırlama veya daha iyi performansla çözer.
Lock arayüzü, ReentrantLock ve ReadWriteLock uygulamalarıyla zaman aşımları, kesilebilir bekleme ve birden çok Condition kuyruğu sağlar. Synchronized'dan daha esnektir ancak finally içinde açıkça serbest bırakılması gerekir, bu da unlock unutulursa hata riskini artırır.
AtomicInteger, AtomicLong, AtomicReference ve diğer sınıflar, CAS'a (Compare-And-Swap) dayalı Lock-Free algoritmaları kullanır. Orta düzey rekabet senaryolarında synchronized'dan önemli ölçüde daha hızlıdırlar çünkü iş parçacıklarını bloke etmezler, işletim sistemi çekirdek bağlam değişimi gerektirmeden iyimser yeniden denemeler yaparlar.
ThreadLocal alternatif bir yaklaşım sunar: her ThreadLocal değişkeni tek bir iş parçacığı içinde yalıtılmıştır ve okuma ve yazma için senkronizasyon gerektirmez. Bu, iş parçacıkları arasında paylaşılmaması gereken veriler için synchronized ihtiyacını tamamen ortadan kaldırır. ThreadLocal, framework'lerde (Spring, Hibernate) işlem bağlamı ve oturumları depolamak için aktif olarak kullanılır.
Android için Kotlin projelerinde, synchronized'a bir alternatif kotlinx.coroutines'den Mutex'tir. İşletim sistemi iş parçacığını bloke etmez, kilidin serbest kalmasına kadar coroutine'i askıya alır — bu, havuz iş parçacıklarının verimli kullanılmasını ve kaynak serbest bırakılması için uzun beklemeler sırasında ANR'yi önler.
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
val mutex = Mutex()
var counter = 0
suspend fun safeIncrement() {
mutex.withLock {
counter++
}
}
Synchronized'ın performansı son Java sürümlerinde önemli ölçüde değişmiştir. Eskiden “ağır” bir mekanizma olarak kabul edilirdi, ancak modern JVM'ler gelişmiş JIT derleyici optimizasyonları sayesinde ek yükün çoğunu ortadan kaldırmıştır. Sanal makinenin çalışma zamanında senkronize kodu nasıl hızlandırdığına ayrıntılı olarak bakalım.
JVM JIT derleyicisi birkaç optimizasyon uygular: biased locking, kilit her zaman aynı iş parçacığı tarafından alınıyorsa senkronizasyonu ortadan kaldırır; lock coarsening, bitişik synchronized bloklarını birleştirir; lock elimination, nesneye yalnızca bir iş parçacığı erişebiliyorsa senkronizasyonu kaldırır. Bu optimizasyonlar, düşük rekabette synchronized'ı neredeyse ücretsiz hale getirir.
JVM her nesne için rekabet seviyesini belirler: rekabet olmadığında biased locking etkinleştirilir; ikinci bir iş parçacığı göründüğünde, kilit spin-bekleme ile lightweight moduna geçer; ve yalnızca uzun süreli bekleme sırasında sistem mutex'i ile heavyweight'e yükselir. Bu yükselme otomatik olarak gerçekleşir ve geliştiricinin manuel olarak bir strateji seçmesi gerekmez.
Modern karşılaştırmalarda (Java 17+), synchronized düşük ve orta rekabette ReentrantLock ile karşılaştırılabilir performans gösterir. Yüksek rekabette Lock, zaman aşımı ve kesme desteğiyle daha verimli bir bekleme kuyruğu sayesinde avantaja sahip olabilir. Rekabetin sabit olduğu yüksek yük sistemlerinde, fair modlu ReentrantLock daha öngörülebilir davranış sağlar.
Atomik sınıflar (AtomicInteger, AtomicReference), CAS tabanlı Lock-Free uygulaması sayesinde basit sayaçlar ve bayraklar için en hızlı olmaya devam eder. İş parçacıklarını hiç bloke etmezler — çakışma durumunda, işlem basitçe bir döngüde yeniden dener. Bu, 4-8 iş parçacığıyla sayaç artırma işlemlerinde synchronized'a kıyasla 3-5 kat performans artışı sağlar.
Sıkça Sorulan Sorular
Synchronized hem karşılıklı dışlama hem de görünürlük sağlar. Volatile yalnızca değişikliklerin görünürlüğünü garanti eder — volatile bir değişkene yazmak tüm iş parçacıklarına görünür ancak eşzamanlı değişikliği engellemez, yani yarış durumuna karşı koruma sağlamaz.
Evet, farklı monitör sırasıyla iç içe senkronizasyonda deadlock mümkündür. Örneğin, bir iş parçacığı synchronized(a) { synchronized(b) } çağırırken, diğeri synchronized(b) { synchronized(a) } çağırır. İç içe synchronized bloklardan kaçının veya tutarlı bir monitör sırası belirleyin.
Monitör, her Java nesnesiyle ilişkili bir senkronizasyon mekanizmasıdır. Bu nesne üzerinde yalnızca bir iş parçacığının synchronized kodu yürütmesini garanti eder. Monitör, bir kilit, bir bekleme kuyruğu ve wait/notify yoluyla bildirim bekleyen iş parçacıklarından oluşan bir havuz içerir.
Modern Java sürümlerinde (17+), synchronized JIT optimizasyonları (biased locking, lock coarsening) sayesinde performansta Lock'tan geri kalmaz. Lock, hız için değil, zaman aşımı, kesilebilir bekleme ve birden çok Condition kuyruğu gibi ek yetenekler için tercih edilir.
Statik synchronized metot, örnek değil, verilen sınıfın Class nesnesinin monitörünü kullanır. Bu, senkronizasyonun sınıfın tüm örneklerine uygulandığı anlamına gelir. Statik olmayan ve statik synchronized metotlar farklı monitörler kullanır ve birbirlerini bloke etmezler.
Özet
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.
Ayrıca okuyun