Zgadzam się z jednym z komentarzem Jana: Należy zawsze używać ostatecznej blokady manekina podczas dostępu nieostatecznej zmienną, aby zapobiec niespójności w przypadku zmian referencyjnych zmiennej. Tak więc w każdym przypadku i jako pierwsza zasada:
Zasada nr 1: Jeśli pole nie jest ostateczne, zawsze używaj (prywatnego) atrapy ostatecznej blokady.
Powód 1: Trzymasz blokadę i samodzielnie zmieniasz odniesienie do zmiennej. Kolejny wątek oczekujący na zewnątrz zsynchronizowanego zamka będzie mógł wejść do strzeżonego bloku.
Powód # 2: Trzymasz blokadę, a inny wątek zmienia odniesienie do zmiennej. Wynik jest taki sam: inny wątek może wejść do strzeżonego bloku.
Ale kiedy używasz ostatecznej atrapy blokady, pojawia się inny problem : możesz otrzymać nieprawidłowe dane, ponieważ twój nie ostateczny obiekt zostanie zsynchronizowany z pamięcią RAM tylko podczas wywoływania synchronizacji (obiekt). A więc jako druga praktyczna zasada:
Zasada nr 2: Podczas blokowania obiektu, który nie jest ostateczny, zawsze musisz zrobić jedno i drugie: Użyć atrapy ostatecznej blokady i blokady obiektu innego niż ostateczny w celu synchronizacji pamięci RAM. (Jedyną alternatywą będzie zadeklarowanie wszystkich pól obiektu jako niestabilnych!)
Te blokady są również nazywane „zamkami zagnieżdżonymi”. Pamiętaj, że musisz dzwonić do nich zawsze w tej samej kolejności, w przeciwnym razie otrzymasz martwy zamek :
public class X {
private final LOCK;
private Object o;
public void setO(Object o){
this.o = o;
}
public void x() {
synchronized (LOCK) {
synchronized(o){
}
}
}
}
Jak widać, piszę dwa zamki bezpośrednio w tej samej linii, ponieważ zawsze należą do siebie. W ten sposób możesz nawet wykonać 10 blokad zagnieżdżania:
synchronized (LOCK1) {
synchronized (LOCK2) {
synchronized (LOCK3) {
synchronized (LOCK4) {
}
}
}
}
Zauważ, że ten kod nie zepsuje się, jeśli po prostu zdobędziesz wewnętrzny zamek, tak jak synchronized (LOCK3)
w przypadku innych wątków. Ale zepsuje się, jeśli wywołasz inny wątek, coś takiego:
synchronized (LOCK4) {
synchronized (LOCK1) {
synchronized (LOCK3) {
synchronized (LOCK2) {
}
}
}
}
Istnieje tylko jedno obejście takich zagnieżdżonych blokad podczas obsługi pól innych niż końcowe:
Zasada # 2 - Alternatywa: Zadeklaruj wszystkie pola obiektu jako niestabilne. (Nie będę tu mówić o wadach robienia tego, np. Zapobieganie przechowywaniu w pamięci podręcznej na poziomie x, nawet dla odczytów, aso.)
Dlatego aioobe ma rację: po prostu użyj java.util.concurrent. Lub zacznij rozumieć wszystko o synchronizacji i zrób to sam z zagnieżdżonymi zamkami. ;)
Aby uzyskać więcej informacji, dlaczego synchronizacja w polach innych niż ostateczne się psuje, zajrzyj do mojego przypadku testowego: https://stackoverflow.com/a/21460055/2012947
Aby uzyskać więcej informacji, dlaczego w ogóle potrzebujesz synchronizacji z powodu pamięci RAM i pamięci podręcznych, zajrzyj tutaj: https://stackoverflow.com/a/21409975/2012947
o
odwołuje w momencie osiągnięcia zsynchronizowanego bloku. Jeśli obiekt, któryo
odnosi się do zmian, może przyjść inny wątek i wykonać zsynchronizowany blok kodu.