Pytasz w szczególności o to, jak działają wewnętrznie , więc oto:
Brak synchronizacji
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
Zasadniczo odczytuje wartość z pamięci, zwiększa ją i przywraca do pamięci. Działa to w jednym wątku, ale obecnie, w dobie wielordzeniowych, wieloprocesorowych i wielopoziomowych pamięci podręcznych, nie będzie działać poprawnie. Przede wszystkim wprowadza warunek wyścigu (kilka wątków może odczytać wartość w tym samym czasie), ale także problemy z widocznością. Wartość może być przechowywana tylko w „ lokalnej ” pamięci procesora (część pamięci podręcznej) i nie może być widoczna dla innych procesorów / rdzeni (a zatem - wątków). Dlatego wiele osób odnosi się do lokalnej kopii zmiennej w wątku. To jest bardzo niebezpieczne. Rozważ ten popularny, ale zepsuty kod zatrzymujący wątek:
private boolean stopped;
public void run() {
while(!stopped) {
//do some work
}
}
public void pleaseStop() {
stopped = true;
}
Dodaj volatiledo stoppedzmiennej i działa dobrze - jeśli jakikolwiek inny wątek zmodyfikuje stoppedzmienną pleaseStop()metodą, masz gwarancję, że zobaczysz tę zmianę natychmiast w while(!stopped)pętli działającego wątku . BTW to nie jest dobry sposób na przerwanie wątku, zobacz: Jak zatrzymać wątek działający na zawsze bez użycia i Zatrzymywanie określonego wątku Java .
AtomicInteger
private AtomicInteger counter = new AtomicInteger();
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
AtomicIntegerZastosowania klasy CAS ( compare-and-swap ) operacje niskiego poziomu CPU (brak synchronizacji potrzebne!) Pozwalają one modyfikować konkretnej zmiennej tylko wtedy, gdy wartość bieżąca jest równa coś innego (i jest zwracany z powodzeniem). Więc kiedy go wykonasz getAndIncrement(), faktycznie działa w pętli (uproszczona rzeczywista implementacja):
int current;
do {
current = get();
} while(!compareAndSet(current, current + 1));
Więc w zasadzie: czytaj; spróbuj zapisać wartość przyrostową; jeśli się nie powiedzie (wartość nie jest już równa current), przeczytaj i spróbuj ponownie. compareAndSet()Jest realizowany w kodzie natywnym (montaż).
volatile bez synchronizacji
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
Ten kod jest nieprawidłowy. Naprawiono problem z widocznością ( volatileupewnia się, że inne wątki mogą zobaczyć zmiany wprowadzone counter), ale nadal ma warunki wyścigu. Zostało to wyjaśnione wiele razy: inkrementacja przed / po nie jest atomowa.
Jedynym efektem ubocznym volatilejest „ opróżnianie ” pamięci podręcznej, aby wszystkie inne podmioty widziały najświeższą wersję danych. W większości sytuacji jest to zbyt surowe; dlatego volatilenie jest domyślny.
volatile bez synchronizacji (2)
volatile int i = 0;
void incIBy5() {
i += 5;
}
Ten sam problem jak powyżej, ale jeszcze gorzej, ponieważ inie jest private. Wyścig jest nadal obecny. Dlaczego to jest problem? Jeśli powiedzmy, że dwa wątki uruchamiają ten kod jednocześnie, wynikiem może być + 5lub + 10. Masz jednak gwarancję, że zobaczysz zmianę.
Wiele niezależnych synchronized
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Niespodzianka, ten kod jest również niepoprawny. W rzeczywistości jest to całkowicie błędne. Przede wszystkim synchronizujesz i, który ma się zmienić (co więcej, ijest prymitywny, więc myślę, że synchronizujesz tymczasowy Integerutworzony przez autoboxing ...) Całkowicie wadliwy. Możesz także napisać:
synchronized(new Object()) {
//thread-safe, SRSLy?
}
Żadne dwa wątki nie mogą wejść do tego samego synchronizedbloku z tym samym zamkiem . W tym przypadku (i podobnie w kodzie) obiekt blokady zmienia się przy każdym wykonaniu, więc synchronizedskutecznie nie ma żadnego efektu.
Nawet jeśli użyłeś ostatniej zmiennej (lub this) do synchronizacji, kod jest nadal niepoprawny. Dwa wątki mogą najpierw zapoznać isię tempsynchronicznie (o tej samej wartości lokalnie temp), to pierwszy przypisuje nową wartość i(powiedzmy od 1 do 6), a drugi robi to samo (od 1 do 6).
Synchronizacja musi rozciągać się od odczytu do przypisania wartości. Twoja pierwsza synchronizacja nie ma żadnego efektu (czytanie intjest atomowa), a także druga. Moim zdaniem są to prawidłowe formularze:
void synchronized incIBy5() {
i += 5
}
void incIBy5() {
synchronized(this) {
i += 5
}
}
void incIBy5() {
synchronized(this) {
int temp = i;
i = temp + 5;
}
}