John'un yorumlarından birine katılıyorum: Değişkenin referans değişiklikleri durumunda tutarsızlıkları önlemek için son olmayan bir değişkene erişirken daima bir son kilit kukla kullanmalısınız. Yani her durumda ve ilk kural olarak:
Kural 1: Bir alan nihai değilse, her zaman bir (özel) son kilit kukla kullanın.
Neden # 1: Kilidi tutuyorsunuz ve değişkenin referansını kendiniz değiştiriyorsunuz. Senkronize kilidin dışında bekleyen başka bir iş parçacığı, korumalı bloğa girebilir.
Neden # 2: Kilidi tutuyorsunuz ve başka bir iş parçacığı değişkenin referansını değiştiriyor. Sonuç aynıdır: Korunan bloğa başka bir iş parçacığı girebilir.
Ancak son bir kilit kukla kullanıldığında, başka bir sorun daha vardır : Yanlış veri alabilirsiniz, çünkü nihai olmayan nesneniz yalnızca senkronizasyon (nesne) çağrılırken RAM ile senkronize edilir. Dolayısıyla, ikinci bir kural olarak:
Kural # 2: Nihai olmayan bir nesneyi kilitlerken, her zaman ikisini de yapmanız gerekir: RAM senkronizasyonu için son kilit kukla ve nihai olmayan nesnenin kilidi kullanma. (Tek alternatif, nesnenin tüm alanlarını değişken olarak ilan etmek olacaktır!)
Bu kilitlere "iç içe kilitler" de denir. Onları her zaman aynı sırayla aramanız gerektiğini unutmayın, aksi takdirde kilitleneceksiniz :
public class X {
private final LOCK;
private Object o;
public void setO(Object o){
this.o = o;
}
public void x() {
synchronized (LOCK) {
synchronized(o){
}
}
}
}
Gördüğünüz gibi iki kilidi doğrudan aynı satıra yazıyorum, çünkü bunlar her zaman birbirine aittir. Bunun gibi, 10 iç içe geçme kilidi bile yapabilirsiniz:
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
}
}
}
}
synchronized (LOCK3)
Başka bir iş parçacığında olduğu gibi sadece bir iç kilit alırsanız bu kodun kırılmayacağını unutmayın . Ancak başka bir ileti dizisinde şunun gibi bir şey çağırırsanız kırılır:
synchronized (LOCK4) {
synchronized (LOCK1) {
synchronized (LOCK3) {
synchronized (LOCK2) {
}
}
}
}
Son olmayan alanları işlerken bu tür iç içe geçmiş kilitler etrafında tek bir geçici çözüm vardır:
Kural # 2 - Alternatif: Nesnenin tüm alanlarını geçici olarak bildirin. (Burada bunu yapmanın dezavantajlarından söz etmeyeceğim, örneğin, okumalar için bile x seviyesi önbelleklerde herhangi bir depolamayı önlemek gibi.)
Bu nedenle aioobe oldukça haklı: Sadece java.util.concurrent kullanın. Veya senkronizasyonla ilgili her şeyi anlamaya başlayın ve bunu iç içe geçmiş kilitlerle kendiniz yapın. ;)
Son olmayan alanlardaki senkronizasyonun neden bozulduğuyla ilgili daha fazla ayrıntı için, test durumuma bir göz atın: https://stackoverflow.com/a/21460055/2012947
Ve RAM ve önbellekler nedeniyle neden senkronize olmanız gerektiğine dair daha fazla ayrıntı için buraya bir göz atın: https://stackoverflow.com/a/21409975/2012947
o
senkronize blok ulaşıldı zamana gönderme. Nesneo
değişikliklere atıfta bulunursa , başka bir iş parçacığı gelebilir ve senkronize edilmiş kod bloğunu çalıştırabilir.