Saya setuju dengan salah satu komentar John: Anda harus selalu menggunakan dummy kunci terakhir saat mengakses variabel non-final untuk mencegah ketidakkonsistenan jika referensi variabel berubah. Jadi dalam kasus apa pun dan sebagai aturan praktis pertama:
Aturan # 1: Jika sebuah field bukan final, selalu gunakan dummy kunci final (private).
Alasan # 1: Anda memegang kunci dan mengubah referensi variabel sendiri. Utas lain yang menunggu di luar kunci tersinkronisasi akan dapat memasuki blok yang dijaga.
Alasan # 2: Anda memegang kunci dan utas lain mengubah referensi variabel. Hasilnya sama: utas lain dapat memasuki blok yang dilindungi.
Tetapi ketika menggunakan dummy kunci terakhir, ada masalah lain : Anda mungkin mendapatkan data yang salah, karena objek non-final Anda hanya akan disinkronkan dengan RAM saat memanggil sinkronisasi (objek). Jadi, sebagai aturan praktis kedua:
Aturan # 2: Saat mengunci objek non-final, Anda selalu perlu melakukan keduanya: Menggunakan dummy kunci terakhir dan kunci objek non-final demi sinkronisasi RAM. (Satu-satunya alternatif adalah mendeklarasikan semua bidang objek sebagai volatile!)
Kunci ini juga disebut "kunci bersarang". Perhatikan bahwa Anda harus memanggil mereka selalu dalam urutan yang sama, jika tidak, Anda akan menemui jalan buntu :
public class X {
private final LOCK;
private Object o;
public void setO(Object o){
this.o = o;
}
public void x() {
synchronized (LOCK) {
synchronized(o){
}
}
}
}
Seperti yang Anda lihat, saya menulis kedua gembok itu langsung pada baris yang sama, karena keduanya selalu saling terkait. Seperti ini, Anda bahkan dapat melakukan 10 kunci bersarang:
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
}
}
}
}
Perhatikan bahwa kode ini tidak akan rusak jika Anda hanya mendapatkan kunci dalam seperti synchronized (LOCK3)
pada utas lainnya. Tetapi itu akan rusak jika Anda memanggil di utas lain sesuatu seperti ini:
synchronized (LOCK4) {
synchronized (LOCK1) {
synchronized (LOCK3) {
synchronized (LOCK2) {
}
}
}
}
Hanya ada satu solusi untuk mengatasi kunci bersarang tersebut saat menangani bidang non-final:
Aturan # 2 - Alternatif: Deklarasikan semua bidang objek sebagai volatile. (Saya tidak akan berbicara di sini tentang kerugian melakukan ini, misalnya mencegah penyimpanan apa pun di cache tingkat x bahkan untuk pembacaan, aso.)
Jadi oleh karena itu aioobe cukup tepat: Gunakan saja java.util.concurrent. Atau mulailah memahami segala sesuatu tentang sinkronisasi dan lakukan sendiri dengan kunci bersarang. ;)
Untuk detail lebih lanjut mengapa sinkronisasi pada bidang non-final rusak, lihat kasus pengujian saya: https://stackoverflow.com/a/21460055/2012947
Dan untuk lebih jelasnya mengapa Anda perlu disinkronkan sama sekali karena RAM dan cache, lihat di sini: https://stackoverflow.com/a/21409975/2012947
o
dirujuk pada saat blok yang disinkronkan tercapai. Jika objek yango
merujuk ke perubahan, thread lain dapat datang dan menjalankan blok kode tersinkronisasi.