Synchronized adalah mekanisme sinkronisasi bawaan dalam bahasa Java yang menyediakan akses eksklusif ke bagian kritis kode. Menurut Oracle, 2024, pengubah synchronized menjamin bahwa hanya satu thread yang dapat menjalankan metode atau blok yang ditandai pada satu waktu tertentu. Mekanisme ini didasarkan pada monitor — konsep fundamental sistem operasi yang memastikan pengoperasian aplikasi multithread yang benar di semua tingkat kompleksitas.
Poin utama
Synchronized adalah kata kunci di Java yang menjamin bahwa hanya satu thread pada satu waktu yang menjalankan bagian kode yang dilindungi, mencegah kerusakan data saat akses paralel. Ini muncul di versi pertama Java dan tetap menjadi cara paling sederhana untuk memastikan keamanan thread bagi pengembang di semua tingkat keahlian.
Pengubah synchronized menyelesaikan dua tugas: saling eksklusi (mutual exclusion) dan visibilitas perubahan (visibility). Ketika sebuah thread meninggalkan blok synchronized, semua perubahan dijamin terlihat oleh thread lain yang memasuki blok yang disinkronkan pada objek yang sama.
Synchronized dapat diterapkan ke seluruh metode atau ke blok kode sembarang dengan menentukan objek-monitor. Dalam kedua kasus, JVM menyisipkan instruksi monitorenter dan monitorexit di level bytecode.
Dalam aplikasi multithread tanpa sinkronisasi, terjadi kondisi balapan (race condition) — ketika dua thread secara bersamaan mengubah data yang sama, menyebabkan hasil yang tidak terduga. Synchronized menjadi alat pertama dan utama Java untuk mengatasi masalah ini, menyediakan sintaks deklaratif sederhana yang dapat diakses oleh setiap pengembang.
Mekanisme synchronized didasarkan pada konsep monitor — primitif sinkronisasi tingkat tinggi yang tertanam di setiap objek Java. Monitor dikaitkan dengan objek saat pertama kali menggunakan blok synchronized padanya.
Setiap objek di Java memiliki monitor yang terkait. Ketika sebuah thread memasuki blok synchronized, ia mengambil alih monitor objek. Jika monitor sudah digunakan oleh thread lain, thread tersebut diblokir hingga dibebaskan. Dalam bytecode, ini sesuai dengan pasangan instruksi monitorenter dan monitorexit.
JVM mengoptimalkan synchronized melalui beberapa tingkat: biased locking (penguncian condong) untuk akses single-thread, lightweight locking (penguncian ringan) saat persaingan rendah, dan heavyweight locking (penguncian berat) saat persaingan intensif dengan partisipasi OS. Tingkat-tingkat ini meningkatkan kinerja tanpa mengubah kode.
class Counter {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
Synchronized menetapkan relasi happens-before: semua tindakan dalam thread sebelum meninggalkan blok synchronized terlihat oleh thread lain setelah memasuki blok yang disinkronkan pada objek yang sama. Ini menjamin tidak hanya saling eksklusi, tetapi juga konsistensi data untuk semua thread.
Java menawarkan dua cara untuk menerapkan synchronized: di tingkat metode dan di tingkat blok. Pilihan di antara keduanya mempengaruhi kinerja dan granularitas sinkronisasi.
Dengan menandai metode dengan pengubah synchronized, Anda secara otomatis menyinkronkannya pada instance saat ini (untuk metode biasa) atau pada objek Class (untuk metode statis). Ini adalah cara paling sederhana untuk memastikan saling eksklusi, tetapi seringkali berlebihan jika bagian kritis hanya merupakan sebagian kecil dari metode dan kode lainnya tidak memerlukan sinkronisasi.
Blok synchronized memberikan kontrol yang tepat: Anda menentukan objek-monitor dan menyinkronkan hanya bagian kode yang diperlukan, meninggalkan sisa metode di luar penguncian. Ini meminimalkan waktu penahanan monitor dan meningkatkan kinerja keseluruhan aplikasi di lingkungan multithread, karena thread lain dapat menjalankan kode yang tidak terkait secara paralel tanpa menunggu pelepasan monitor.
class DataProcessor {
private final Object lock = new Object();
public void process() {
// kode di luar seksi kritis - tanpa sinkronisasi
prepareData()
synchronized (lock) {
// hanya blok ini yang dilindungi
updateSharedState()
}
// lanjutan tanpa penguncian
cleanup()
}
}
| Kriteria | Metode synchronized | Blok synchronized |
|---|---|---|
| Monitor | this (instance) atau Class | objek apa pun |
| Granularitas | seluruh metode | hanya kode yang diperlukan |
| Keterbacaan | tinggi | sedang |
| Kinerja | lebih rendah untuk metode besar | lebih tinggi untuk bagian kritis kecil |
Dalam pengembangan Android, synchronized banyak digunakan untuk melindungi SharedPreferences, akses database, dan komponen UI. Namun, penggunaannya di thread utama sangat tidak disarankan karena risiko pembekuan antarmuka.
SharedPreferences di Android menyediakan keamanan thread dasar, tetapi saat diedit oleh beberapa thread melalui Editor, sinkronisasi eksternal mungkin diperlukan. Blok synchronized dengan objek penguncian terpisah menjamin konsistensi perubahan.
class PreferencesManager(private val prefs: SharedPreferences) {
private val lock = Any()
fun writeToken(token: String) {
synchronized (lock) {
prefs.edit()
.putString("auth_token", token)
.apply()
}
}
}
Keterbatasan utama synchronized di Android — pemblokiran thread. Tidak seperti coroutine dengan Mutex, synchronized memblokir seluruh thread sistem. Di thread utama, ini menyebabkan ANR. Dalam pengembangan Android modern, disarankan untuk mengganti synchronized dengan coroutine (suspend Mutex) atau tipe atomik (AtomicInteger).
Java dan Kotlin modern menawarkan beberapa alternatif untuk synchronized, yang masing-masing menyelesaikan tugas yang sama dengan lebih sedikit keterbatasan atau kinerja yang lebih baik.
Antarmuka Lock dengan implementasi ReentrantLock dan ReadWriteLock menyediakan batas waktu, penantian yang dapat diinterupsi, dan beberapa antrian Condition. Ini lebih fleksibel daripada synchronized, tetapi memerlukan pelepasan eksplisit di finally, yang meningkatkan risiko kesalahan karena unlock yang terlupakan.
Kelas AtomicInteger, AtomicLong, AtomicReference dan lainnya menggunakan algoritma Lock-Free berbasis CAS (Compare-And-Swap). Mereka secara signifikan lebih cepat daripada synchronized dalam skenario dengan persaingan sedang, karena tidak memblokir thread, melainkan melakukan percobaan ulang optimistis dan tidak memerlukan peralihan konteks oleh kernel OS.
ThreadLocal menyediakan pendekatan alternatif: setiap variabel ThreadLocal diisolasi dalam lingkup satu thread dan tidak memerlukan sinkronisasi untuk membaca dan menulis. Ini sepenuhnya menghilangkan kebutuhan akan synchronized untuk data yang tidak boleh dibagi antar thread. ThreadLocal secara aktif digunakan dalam framework (Spring, Hibernate) untuk menyimpan konteks transaksi dan sesi.
Dalam proyek Kotlin untuk Android, alternatif untuk synchronized adalah Mutex dari kotlinx.coroutines. Ini tidak memblokir thread sistem operasi, melainkan menangguhkan coroutine hingga penguncian dilepaskan — ini memungkinkan penggunaan thread pool secara efisien dan menghindari ANR saat menunggu lama untuk pelepasan sumber daya.
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
val mutex = Mutex()
var counter = 0
suspend fun safeIncrement() {
mutex.withLock {
counter++
}
}
Kinerja synchronized telah berubah secara signifikan di versi Java terbaru. Sebelumnya dianggap sebagai mekanisme yang "berat", tetapi JVM modern telah menghilangkan sebagian besar overhead berkat optimalisasi lanjutan dari kompiler JIT. Mari kita lihat secara detail bagaimana mesin virtual mempercepat kode yang disinkronkan saat runtime.
Kompiler JIT JVM menerapkan beberapa optimalisasi: biased locking menghilangkan sinkronisasi jika penguncian selalu diambil oleh satu thread; lock coarsening menggabungkan blok synchronized yang berdekatan menjadi satu; lock elimination menghapus sinkronisasi jika objek hanya dapat diakses oleh satu thread. Optimalisasi ini membuat synchronized praktis gratis pada persaingan rendah.
JVM menentukan tingkat persaingan untuk setiap objek: saat tidak ada persaingan, biased locking diaktifkan; saat thread kedua muncul, penguncian beralih ke mode lightweight dengan spin-waiting; dan hanya saat penantian lama — ke heavyweight dengan mutex sistem. Eskalasi ini terjadi secara otomatis dan pengembang tidak perlu memilih strategi secara manual.
Dalam benchmark modern (Java 17+) synchronized menunjukkan kinerja yang sebanding dengan ReentrantLock pada persaingan rendah dan sedang. Pada persaingan tinggi, Lock mungkin memiliki keunggulan berkat antrian penantian yang lebih efisien dengan dukungan batas waktu dan interupsi. Untuk sistem dengan beban tinggi di mana persaingan konstan, ReentrantLock dalam mode fair memberikan perilaku yang lebih dapat diprediksi.
Kelas atomik (AtomicInteger, AtomicReference) tetap menjadi yang tercepat untuk penghitung dan bendera sederhana berkat implementasi Lock-Free pada CAS. Mereka sama sekali tidak memblokir thread — saat konflik, operasi hanya diulang dalam loop. Ini memberikan peningkatan kinerja 3-5 kali lipat dibandingkan dengan synchronized pada operasi kenaikan penghitung dengan 4-8 thread.
Pertanyaan yang sering diajukan
Synchronized menyediakan saling eksklusi dan visibilitas. Volatile hanya menjamin visibilitas perubahan — penulisan ke variabel volatile terlihat oleh semua thread, tetapi tidak mencegah perubahan simultan, artinya tidak melindungi dari kondisi balapan.
Ya, deadlock mungkin terjadi pada sinkronisasi bersarang dengan urutan monitor yang berbeda. Misalnya, satu thread memanggil synchronized(a) { synchronized(b) }, dan yang lain — synchronized(b) { synchronized(a) }. Hindari blok synchronized bersarang atau tetapkan urutan monitor yang tetap.
Monitor adalah mekanisme sinkronisasi yang terkait dengan setiap objek Java. Ini menjamin bahwa hanya satu thread yang mengeksekusi kode synchronized pada objek tersebut. Monitor mencakup penguncian, antrian penantian, dan kumpulan thread yang menunggu pemberitahuan melalui wait/notify.
Di versi Java modern (17+) synchronized tidak kalah dengan Lock dalam hal kinerja berkat optimalisasi JIT (biased locking, lock coarsening). Lock lebih dipilih bukan karena kecepatan, tetapi karena kemampuan tambahan: batas waktu, penantian yang dapat diinterupsi, dan beberapa Condition.
Metode statis synchronized menggunakan monitor objek Class dari kelas tersebut, bukan instance. Ini berarti sinkronisasi mencakup semua instance kelas. Metode synchronized non-statis dan statis menggunakan monitor yang berbeda dan tidak saling memblokir.
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