Jak atomowy / lotny / zsynchronizowany działa wewnętrznie?
Jaka jest różnica między następującymi blokami kodu?
Kod 1
private int counter;
public int getNextUniqueIndex() {
return counter++;
}
Kod 2
private AtomicInteger counter;
public int getNextUniqueIndex() {
return counter.getAndIncrement();
}
Kod 3
private volatile int counter;
public int getNextUniqueIndex() {
return counter++;
}
Czy volatile
działa w następujący sposób? Jest
volatile int i = 0;
void incIBy5() {
i += 5;
}
równoważny
Integer i = 5;
void incIBy5() {
int temp;
synchronized(i) { temp = i }
synchronized(i) { i = temp + 5 }
}
Myślę, że dwa wątki nie mogą jednocześnie wejść do zsynchronizowanego bloku ... mam rację? Jeśli to prawda, to jak to atomic.incrementAndGet()
działa bez synchronized
? I czy jest bezpieczny dla wątków?
Jaka jest różnica między wewnętrznym odczytem a zapisem do zmiennych lotnych / zmiennych atomowych? W pewnym artykule przeczytałem, że wątek ma lokalną kopię zmiennych - co to jest?
Odpowiedzi:
Pytasz w szczególności o to, jak działają wewnętrznie , więc oto:
Brak synchronizacji
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:
Dodaj
volatile
dostopped
zmiennej i działa dobrze - jeśli jakikolwiek inny wątek zmodyfikujestopped
zmiennąpleaseStop()
metodą, masz gwarancję, że zobaczysz tę zmianę natychmiast wwhile(!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
AtomicInteger
Zastosowania 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 wykonaszgetAndIncrement()
, faktycznie działa w pętli (uproszczona rzeczywista implementacja):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 synchronizacjiTen kod jest nieprawidłowy. Naprawiono problem z widocznością (
volatile
upewnia się, że inne wątki mogą zobaczyć zmiany wprowadzonecounter
), ale nadal ma warunki wyścigu. Zostało to wyjaśnione wiele razy: inkrementacja przed / po nie jest atomowa.Jedynym efektem ubocznym
volatile
jest „ 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; dlategovolatile
nie jest domyślny.volatile
bez synchronizacji (2)Ten sam problem jak powyżej, ale jeszcze gorzej, ponieważ
i
nie jestprivate
. 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ć+ 5
lub+ 10
. Masz jednak gwarancję, że zobaczysz zmianę.Wiele niezależnych
synchronized
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,i
jest prymitywny, więc myślę, że synchronizujesz tymczasowyInteger
utworzony przez autoboxing ...) Całkowicie wadliwy. Możesz także napisać:Żadne dwa wątki nie mogą wejść do tego samego
synchronized
bloku z tym samym zamkiem . W tym przypadku (i podobnie w kodzie) obiekt blokady zmienia się przy każdym wykonaniu, więcsynchronized
skutecznie 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ći
siętemp
synchronicznie (o tej samej wartości lokalnietemp
), 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
int
jest atomowa), a także druga. Moim zdaniem są to prawidłowe formularze:źródło
compareAndSet
to tylko cienkie opakowanie wokół operacji CAS. W odpowiedzi podaję kilka szczegółów.Uznanie zmiennej za niestabilną oznacza, że modyfikacja jej wartości natychmiast wpływa na faktyczne miejsce w pamięci dla zmiennej. Kompilator nie może zoptymalizować żadnych odniesień do zmiennej. Gwarantuje to, że gdy jeden wątek zmodyfikuje zmienną, wszystkie pozostałe wątki natychmiast zobaczą nową wartość. (Nie jest to gwarantowane w przypadku zmiennych nielotnych.)
Zadeklarowanie zmiennej atomowej gwarantuje, że operacje wykonane na zmiennej zachodzą w sposób atomowy, tj. Że wszystkie podetapy operacji są zakończone w wątku, w którym są wykonywane i nie są przerywane przez inne wątki. Na przykład operacja inkrementacji i testowania wymaga inkrementacji zmiennej, a następnie porównania z inną wartością; operacja atomowa gwarantuje, że oba te etapy zostaną wykonane tak, jakby były pojedynczą niepodzielną / nieprzerwaną operacją.
Synchronizacja wszystkich dostępów do zmiennej pozwala na dostęp tylko do jednego wątku na raz i zmusza wszystkie pozostałe wątki do oczekiwania na dostęp do wątku, który zwolni dostęp do zmiennej.
Dostęp zsynchronizowany jest podobny do dostępu atomowego, ale operacje atomowe są generalnie realizowane na niższym poziomie programowania. Jest również całkowicie możliwe zsynchronizowanie tylko niektórych dostępów do zmiennej i umożliwienie niezsynchronizowania innych dostępów (np. Zsynchronizowanie wszystkich zapisów do zmiennej, ale żadnego odczytu z niej).
Atomowość, synchronizacja i zmienność są niezależnymi atrybutami, ale zwykle są używane w połączeniu w celu wymuszenia właściwej współpracy wątków w celu uzyskania dostępu do zmiennych.
Dodatek (kwiecień 2016 r.)
Zsynchronizowany dostęp do zmiennej jest zwykle realizowany za pomocą monitora lub semafora . Są to mechanizmy mutex niskiego poziomu (wzajemne wykluczanie), które pozwalają wątkowi przejąć kontrolę wyłącznie nad zmienną lub blokiem kodu, zmuszając wszystkie pozostałe wątki do czekania, jeśli również spróbują uzyskać ten sam muteks. Gdy wątek będący właścicielem zwolni muteks, kolejny wątek może po kolei uzyskać muteks.
Dodatek (lipiec 2016 r.)
Synchronizacja odbywa się na obiekcie . Oznacza to, że wywołanie zsynchronizowanej metody klasy zablokuje
this
obiekt wywołania. Zsynchronizowane statycznie metody zablokująClass
sam obiekt.Podobnie, wprowadzenie zsynchronizowanego bloku wymaga zablokowania
this
obiektu metody.Oznacza to, że zsynchronizowana metoda (lub blok) może być wykonywana w wielu wątkach jednocześnie, jeśli blokują one różne obiekty, ale tylko jeden wątek może wykonać metodę synchroniczną (lub blok) jednocześnie dla dowolnego pojedynczego obiektu.
źródło
lotny:
volatile
jest słowem kluczowym.volatile
zmusza wszystkie wątki do pobrania najnowszej wartości zmiennej z pamięci głównej zamiast z pamięci podręcznej. Aby uzyskać dostęp do zmiennych zmiennych, nie jest wymagane blokowanie. Wszystkie wątki mogą jednocześnie uzyskiwać dostęp do zmiennej wartości zmiennej.Korzystanie ze
volatile
zmiennych zmniejsza ryzyko błędów spójności pamięci, ponieważ każdy zapis do zmiennej lotnej ustanawia relację przed zdarzeniem z kolejnymi odczytami tej samej zmiennej.Oznacza to, że zmiany w
volatile
zmiennej są zawsze widoczne dla innych wątków . Co więcej, oznacza to również, że gdy wątek czytavolatile
zmienną, widzi nie tylko ostatnią zmianę niestabilności, ale także skutki uboczne kodu, który doprowadził do zmiany .Kiedy stosować: Jeden wątek modyfikuje dane, a inne wątki muszą odczytać najnowszą wartość danych. Inne wątki podejmą pewne działania, ale nie zaktualizują danych .
AtomicXXX:
AtomicXXX
klasy obsługują bezpieczne dla wątków programowanie na pojedynczych zmiennych. TeAtomicXXX
klasy (jakAtomicInteger
) rozwiązują błędy niespójności pamięci / skutki uboczne modyfikacji zmiennych lotnych, które były dostępne w wielu wątkach.Kiedy stosować: Wiele wątków może odczytywać i modyfikować dane.
zsynchronizowane:
synchronized
to słowo kluczowe używane do ochrony metody lub bloku kodu. Dzięki synchronizacji metoda ma dwa efekty:Po pierwsze, niemożliwe
synchronized
jest przeplatanie dwóch wywołań metod na tym samym obiekcie. Gdy jeden wątek wykonujesynchronized
metodę dla obiektu, wszystkie inne wątki, które wywołująsynchronized
metody dla tego samego bloku obiektu (zawieszają wykonywanie), dopóki pierwszy wątek nie zostanie zakończony z obiektem.Po drugie, kiedy
synchronized
metoda kończy działanie, automatycznie ustanawia relację przed zdarzeniem z każdym kolejnym wywołaniemsynchronized
metody dla tego samego obiektu. Gwarantuje to, że zmiany stanu obiektu są widoczne dla wszystkich wątków.Kiedy stosować: Wiele wątków może odczytywać i modyfikować dane. Twoja logika biznesowa nie tylko aktualizuje dane, ale także wykonuje operacje atomowe
AtomicXXX
jest równoważne,volatile + synchronized
chociaż implementacja jest inna.AmtomicXXX
rozszerzavolatile
zmienne +compareAndSet
metody, ale nie używa synchronizacji.Powiązane pytania SE:
Różnica między zmienną i zsynchronizowaną w Javie
Volatile boolean vs AtomicBoolean
Dobre artykuły do przeczytania: (Powyższe treści pochodzą z tych stron dokumentacji)
https://docs.oracle.com/javase/tutorial/essential/concurrency/sync.html
https://docs.oracle.com/javase/tutorial/essential/concurrency/atomic.html
https://docs.oracle.com/javase/8/docs/api/java/util/concurrent/atomic/package-summary.html
źródło
Dwa wątki nie mogą dwa razy wejść do zsynchronizowanego bloku tego samego obiektu. Oznacza to, że dwa wątki mogą wprowadzić ten sam blok na różnych obiektach. To zamieszanie może prowadzić do takiego kodu.
Nie będzie to działało zgodnie z oczekiwaniami, ponieważ może za każdym razem blokować inny obiekt.
tak. Nie używa blokowania, aby osiągnąć bezpieczeństwo gwintu.
Jeśli chcesz wiedzieć, jak działają bardziej szczegółowo, możesz przeczytać dla nich kod.
Klasa atomowa wykorzystuje zmienne pola. Nie ma różnicy w terenie. Różnica polega na wykonywanych operacjach. Klasy Atomic używają operacji CompareAndSwap lub CAS.
Mogę jedynie założyć, że odnosi się to do faktu, że każdy procesor ma swój własny widok pamięci podręcznej, który może różnić się od każdego innego procesora. Aby mieć pewność, że Twój procesor ma spójny widok danych, musisz zastosować techniki bezpieczeństwa wątków.
Jest to problem tylko wtedy, gdy pamięć jest współdzielona, co najmniej jeden wątek ją aktualizuje.
źródło
Synchronized Vs Atomic Vs Volatile:
Popraw mnie, jeśli coś mi umknęło.
źródło
Synchronizacja niestabilna + jest niezawodnym rozwiązaniem dla operacji (instrukcji) w pełni atomowej, która zawiera wiele instrukcji dla procesora.
Powiedz na przykład: lotne int i = 2; i ++, czyli nic innego jak i = i + 1; co powoduje, że i jako wartość 3 w pamięci po wykonaniu tej instrukcji. Obejmuje to odczytanie istniejącej wartości z pamięci dla i (czyli 2), załadowanie do rejestru akumulatora procesora i wykonanie obliczeń poprzez zwiększenie istniejącej wartości o jeden (2 + 1 = 3 w akumulatorze), a następnie zapisanie tej zwiększonej wartości powrót do pamięci. Operacje te nie są wystarczająco atomowe, chociaż wartość i jest niestabilna. Będąc niestabilnym gwarantuje tylko, że POJEDYNCZY odczyt / zapis z pamięci jest atomowy, a nie z WIELU. Dlatego musimy zsynchronizować się także z i ++, aby zachować pewność, że jest to dowód atomowy. Pamiętaj, że instrukcja zawiera wiele instrukcji.
Mam nadzieję, że wyjaśnienie jest wystarczająco jasne.
źródło
Zmienny modyfikator Java jest przykładem specjalnego mechanizmu gwarantującego komunikację między wątkami. Kiedy jeden wątek zapisuje zmienną zmienną, a inny wątek widzi, że pisze, pierwszy wątek informuje drugi o całej zawartości pamięci, dopóki nie wykona zapisu do tej zmiennej ulotnej.
Operacje atomowe są wykonywane w jednej jednostce zadania bez ingerencji innych operacji. Operacje atomowe są niezbędne w środowisku wielowątkowym, aby uniknąć niespójności danych.
źródło