Ostrzeżenie jest wyświetlane za każdym razem, gdy przeprowadzam synchronizację w nieostatecznym polu klasy. Oto kod:
public class X
{
private Object o;
public void setO(Object o)
{
this.o = o;
}
public void x()
{
synchronized (o) // synchronization on a non-final field
{
}
}
}
więc zmieniłem kodowanie w następujący sposób:
public class X
{
private final Object o;
public X()
{
o = new Object();
}
public void x()
{
synchronized (o)
{
}
}
}
Nie jestem pewien, czy powyższy kod jest właściwym sposobem synchronizacji w nieostatecznym polu klasy. Jak mogę zsynchronizować pole inne niż końcowe?
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.this
- zalecałbym utworzenie końcowej zmiennej w klasie wyłącznie do celów blokowania , które uniemożliwi innym blokowanie tego samego obiektu.To naprawdę nie jest dobry pomysł - ponieważ zsynchronizowane bloki nie są już tak naprawdę synchronizowane w spójny sposób.
Zakładając, że zsynchronizowane bloki mają zapewniać, że tylko jeden wątek ma dostęp do niektórych udostępnionych danych naraz, rozważ:
Dlaczego chcesz , żeby to się stało? Może są jakieś bardzo wyspecjalizowane sytuacje, w których ma to sens ... ale musiałbyś mi przedstawić konkretny przypadek użycia (wraz ze sposobami złagodzenia tego rodzaju scenariusza, który podałem powyżej), zanim byłbym zadowolony z to.
źródło
o
) - i w trakcie jego wykonywania rozpocząć mutowanie innej listy. Jaki byłby to dobry pomysł? Myślę, że zasadniczo nie zgadzamy się co do tego, czy warto blokować obiekty, których dotykasz w inny sposób, czy nie. Wolałbym raczej móc rozumować na temat mojego kodu bez żadnej wiedzy o tym, co robi inny kod w zakresie blokowania.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){ //do something with 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) { //entering the locked space } } } }
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) { //dead lock! synchronized (LOCK3) { synchronized (LOCK2) { //will never enter here... } } } }
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
źródło
o
z synchronizowanym (LOCK), aby ustanowić relację „zdarza się przed” między ustawieniem a obiektem odczytuo
.Tak naprawdę nie widzę tutaj poprawnej odpowiedzi, to znaczy, jest to całkowicie w porządku.
Nie jestem nawet pewien, dlaczego to ostrzeżenie, nie ma w tym nic złego. JVM upewnia się, że podczas odczytywania wartości otrzymujesz z powrotem jakiś prawidłowy obiekt (lub wartość null) i możesz synchronizować dowolny obiekt.
Jeśli planujesz faktycznie zmienić blokadę, gdy jest ona używana (w przeciwieństwie do np. Zmiany jej z metody init, zanim zaczniesz jej używać), musisz wprowadzić zmienną, którą planujesz zmienić
volatile
. Następnie wszystko, co musisz zrobić, to zsynchronizować zarówno stary, jak i nowy obiekt, i możesz bezpiecznie zmienić wartośćpublic volatile Object lock;
...
synchronized (lock) { synchronized (newObject) { lock = newObject; } }
Tam. To nie jest skomplikowane, pisanie kodu z blokadami (muteksami) jest całkiem proste. Pisanie kodu bez nich (kod bez blokady) jest trudne.
źródło
EDYCJA: Więc to rozwiązanie (zgodnie z sugestią Jona Skeeta) może mieć problem z atomowością implementacji „synchronized (object) {}” podczas zmiany odniesienia do obiektu. Pytałem osobno i według pana Ericksona nie jest to wątkowo bezpieczne - patrz: Czy wejście w synchronizowany blok jest atomowe? . Więc weź to jako przykład, jak tego nie robić - z linkami dlaczego;)
Zobacz kod, jak by działał, gdyby synchronized () był atomowy:
public class Main { static class Config{ char a='0'; char b='0'; public void log(){ synchronized(this){ System.out.println(""+a+","+b); } } } static Config cfg = new Config(); static class Doer extends Thread { char id; Doer(char id) { this.id = id; } public void mySleep(long ms){ try{Thread.sleep(ms);}catch(Exception ex){ex.printStackTrace();} } public void run() { System.out.println("Doer "+id+" beg"); if(id == 'X'){ synchronized (cfg){ cfg.a=id; mySleep(1000); // do not forget to put synchronize(cfg) over setting new cfg - otherwise following will happend // here it would be modifying different cfg (cos Y will change it). // Another problem would be that new cfg would be in parallel modified by Z cos synchronized is applied on new object cfg.b=id; } } if(id == 'Y'){ mySleep(333); synchronized(cfg) // comment this and you will see inconsistency in log - if you keep it I think all is ok { cfg = new Config(); // introduce new configuration // be aware - don't expect here to be synchronized on new cfg! // Z might already get a lock } } if(id == 'Z'){ mySleep(666); synchronized (cfg){ cfg.a=id; mySleep(100); cfg.b=id; } } System.out.println("Doer "+id+" end"); cfg.log(); } } public static void main(String[] args) throws InterruptedException { Doer X = new Doer('X'); Doer Y = new Doer('Y'); Doer Z = new Doer('Z'); X.start(); Y.start(); Z.start(); } }
źródło
AtomicReference odpowiada Twoim wymaganiom.
Z dokumentacji java o pakiecie atomic :
boolean compareAndSet(expectedValue, updateValue);
Przykładowy kod:
String initialReference = "value 1"; AtomicReference<String> someRef = new AtomicReference<String>(initialReference); String newReference = "value 2"; boolean exchanged = someRef.compareAndSet(initialReference, newReference); System.out.println("exchanged: " + exchanged);
W powyższym przykładzie zamieniasz
String
na własnyObject
Powiązane pytanie SE:
Kiedy używać AtomicReference w Javie?
źródło
Jeśli
o
nigdy się nie zmienia przez cały okres istnienia instancjiX
, druga wersja ma lepszy styl, niezależnie od tego, czy wymagana jest synchronizacja.Nie da się odpowiedzieć na pytanie, czy coś jest nie tak z pierwszą wersją, nie wiedząc, co jeszcze dzieje się na tych zajęciach. Zgodziłbym się z kompilatorem, że wygląda na podatny na błędy (nie powtórzę tego, co powiedzieli inni).
źródło
Po prostu dodając moje dwa centy: otrzymałem to ostrzeżenie, gdy użyłem komponentu, który jest tworzony przez projektanta, więc jego pole nie może być tak naprawdę ostateczne, ponieważ konstruktor nie może przyjmować parametrów. Innymi słowy, miałem quasi-końcowe pole bez końcowego słowa kluczowego.
Myślę, że dlatego jest to tylko ostrzeżenie: prawdopodobnie robisz coś złego, ale może też być słuszne.
źródło