Synchronizacja vs Blokada

182

java.util.concurrentInterfejs API zapewnia klasę o nazwie as Lock, która w zasadzie serializowałaby kontrolę w celu uzyskania dostępu do zasobu krytycznego. Daje metodę taką jak park()i unpark().

Możemy zrobić podobne rzeczy, jeśli możemy użyć synchronizedsłowa kluczowego oraz użycia wait()i notify() notifyAll()metod.

Zastanawiam się, który z nich jest lepszy w praktyce i dlaczego?

marzyciel
źródło
1
pomocny artykuł tutaj: javarevisited.blogspot.in/2013/03/…
roottraveller

Odpowiedzi:

178

Jeśli po prostu blokujesz obiekt, wolałbym użyć synchronized

Przykład:

Lock.acquire();
doSomethingNifty(); // Throws a NPE!
Lock.release(); // Oh noes, we never release the lock!

Musisz wyraźnie robić try{} finally{}wszędzie.

Podczas gdy zsynchronizowane, jest bardzo jasne i niemożliwe do popełnienia błędu:

synchronized(myObject) {
    doSomethingNifty();
}

To powiedziawszy, Lockmogą być bardziej przydatne w przypadku bardziej skomplikowanych rzeczy, w których nie można uzyskać i zwolnić w tak czysty sposób. Szczerze mówiąc wolałbym Lockprzede wszystkim unikać korzystania z go, i po prostu zastosować bardziej wyrafinowaną kontrolę współbieżności, taką jak a CyclicBarrierlub a LinkedBlockingQueue, jeśli spełniają one Twoje potrzeby.

Nigdy nie miałem powód do używania wait()albo notify()ale niektóre z nich mogą być dobre.

Steven Schlansker
źródło
1
Jaka jest różnica między oczekiwaniem / powiadomieniem a zaparkowaniem / odparkowaniem LockSupport? docs.oracle.com/javase/7/docs/api/java/util/concurrent/locks/…
Pacerier
6
Na początku przykład miał sens w przypadku zamków, ale potem zdałem sobie sprawę, że jeśli użyjesz bloku w końcu, tego problemu można uniknąć, gdy zamki nie zostaną zwolnione
William Reed
Ahh ... Jedna z tych chwil, aby docenić model RAII w C ++. std::lock_guard
WhiZTiM
67

Zastanawiam się, który z nich jest lepszy w praktyce i dlaczego?

Odkryłem, że Locki Condition(i inne nowe concurrentklasy) to po prostu więcej narzędzi do zestawu narzędzi. Mógłbym zrobić wszystko, czego potrzebowałem, za pomocą mojego starego młota pazurowego ( synchronizedsłowo kluczowe), ale w niektórych sytuacjach korzystanie z niego było niewygodne. Kilka z tych niezręcznych sytuacji stało się o wiele prostszych, gdy dodałem więcej narzędzi do mojego zestawu narzędzi: gumowy młotek, młot kulkowy, prybar i kilka ciosów w gwoździe. Jednak mój stary młot pazur nadal widzi swój udział w użyciu.

Nie sądzę, aby jedno było naprawdę „lepsze” niż drugie, ale raczej każde lepiej pasuje do różnych problemów. W skrócie, prosty model i charakter zorientowany na zakres synchronizedpomaga chronić mnie przed błędami w moim kodzie, ale te same zalety są czasem przeszkodami w bardziej złożonych scenariuszach. To są te bardziej złożone scenariusze, że pakiet pomocniczy został stworzony, aby pomóc rozwiązać. Ale korzystanie z tego konstruktu wyższego poziomu wymaga bardziej wyraźnego i ostrożnego zarządzania kodem.

===

Myślę, że JavaDoc dobrze sobie radzi z opisywaniem rozróżnienia między Locki synchronized(nacisk jest mój):

Implementacje blokady zapewniają szersze operacje blokowania, niż można uzyskać przy użyciu zsynchronizowanych metod i instrukcji. Pozwalają na bardziej elastyczną strukturę , mogą mieć zupełnie inne właściwości i mogą obsługiwać wiele powiązanych obiektów Warunków .

...

Zastosowanie zsynchronizowanych metod lub oświadczeń zapewnia dostęp do niejawnego blokady monitora związanego z każdego przedmiotu, ale siły wszystkim nabywanie i zwalniania blokady nastąpić w sposób blokowej strukturze : kiedy wielu zamki zostały nabyte one muszą zostać wydany w kolejności przeciwnej , a wszystkie blokady muszą być zwolnione w tym samym zakresie leksykalnym, w którym zostały nabyte .

Podczas gdy mechanizm określania zakresu dla zsynchronizowanych metod i instrukcji znacznie ułatwia programowanie przy użyciu blokad monitora i pomaga uniknąć wielu typowych błędów programowania związanych z blokadami, istnieją sytuacje, w których trzeba pracować z blokadami w bardziej elastyczny sposób. Na przykład * * niektóre algorytmy * do przechodzenia przez równolegle dostępne struktury danych wymagają użycia metody „hand-over-hand” lub „chain lock : nabywasz blokadę węzła A, następnie węzła B, a następnie zwalniasz A i nabywasz C, następnie zwolnij B i zdobądź D i tak dalej. Implementacje interfejsu Lock umożliwiają stosowanie takich technik, umożliwiając uzyskanie i zwolnienie blokady w różnych zakresach , orazumożliwiając uzyskanie i zwolnienie wielu zamków w dowolnej kolejności .

Ta zwiększona elastyczność wiąże się z dodatkową odpowiedzialnością . Nieobecność bloku o strukturze blokowania usuwa automatyczne zwolnienie blokad , które pojawiają się przy zsynchronizowanych metod i instrukcji. W większości przypadków należy zastosować następujący idiom:

...

Podczas blokowania i odblokowywania występują w różnych zakresach , należy zachować ostrożność, aby upewnić się, że cały kod, który jest wykonywany, kiedy blokada jest utrzymywana jest chroniony przez Try-końcu lub try-catch , aby upewnić się, że blokada zostanie zwolniona , gdy jest to konieczne.

Implementacje blokady zapewniają dodatkową funkcjonalność w stosunku do stosowania zsynchronizowanych metod i instrukcji, zapewniając nieblokującą próbę uzyskania blokady (tryLock ()), próbę uzyskania blokady, którą można przerwać (lockInterruptrupt (), oraz próbę uzyskania blokada, która może przekroczyć limit czasu (tryLock (long, TimeUnit)).

...

Bert F.
źródło
23

Możesz osiągnąć wszystko to, co media w java.util.concurrent czynienia z prymitywów niskopoziomowych jak synchronized, volatilealbo czekać / zawiadomić

Jednak współbieżność jest trudna i większość ludzi popełnia co najmniej niektóre jej błędy, co powoduje, że ich kod jest niepoprawny lub nieefektywny (lub oba).

Współbieżny interfejs API zapewnia podejście wyższego poziomu, które jest łatwiejsze (i jako takie bezpieczniejsze) w użyciu. W skrócie, nie powinieneś już używać synchronized, volatile, wait, notifybezpośrednio.

Blokada klasa sama w sobie jest po stronie niższego poziomu tego zestawu narzędzi, można nawet nie trzeba używać bezpośrednio albo (można użyć Queuesi Semaphore i rzeczy, etc, większość czasu).

Thilo
źródło
2
Czy zwykłe stare oczekiwanie / powiadomienie jest uważane za prymitywne niższego poziomu niż java.util.concurrent.locks.LockSupport's park / unpark, czy też jest odwrotnie?
Pacerier
@Pacerier: Uważam, że oba są na niskim poziomie (tj. Coś, czego programista aplikacji chciałby uniknąć, używając bezpośrednio), ale z pewnością części java.util.concurrency niższego poziomu (takie jak pakiet blokad) są zbudowane na górze natywnych operacji podstawowych JVM czeka / powiadamia (które są jeszcze niższego poziomu).
Thilo,
2
Nie, mam na myśli 3: Thread.sleep / interrupt, Object.wait / notify, LockSupport.park / unpark, który jest najbardziej prymitywny?
Pacerier
2
@ Thilo Nie jestem pewien, w jaki sposób popierasz swoje oświadczenie, które java.util.concurrentjest łatwiejsze [ogólnie] niż funkcje językowe ( synchronizeditp.). Kiedy używasz java.util.concurrent, musisz przyzwyczaić się do wypełniania lock.lock(); try { ... } finally { lock.unlock() }przed napisaniem kodu, podczas gdy przy pomocy w synchronizedzasadzie wszystko jest w porządku od samego początku. Na tej podstawie powiedziałbym, że synchronizedjest łatwiejsze (biorąc pod uwagę, że chcesz jego zachowania) niż java.util.concurrent.locks.Lock. par 4
Iwan Aucamp
1
Nie sądzę, że można dokładnie odtworzyć zachowanie klas AtomicXXX tylko za pomocą operacji podstawowych współbieżności, ponieważ opierają się one na natywnym wywołaniu CAS niedostępnym przed java.util.concurrent.
Duncan Armstrong
15

Istnieją 4 główne czynniki, dla których chcesz użyć synchronizedlub java.util.concurrent.Lock.

Uwaga: synchroniczne blokowanie jest tym, co mam na myśli, gdy mówię o zamknięciu wewnętrznym.

  1. Kiedy Java 5 pojawiła się z ReentrantLocks, okazało się, że mają dość zauważalną różnicę w przepustowości niż wewnętrzne blokowanie. Jeśli szukasz szybszego mechanizmu blokującego i korzystasz z wersji 1.5, rozważ jucReentrantLock. Wewnętrzne blokowanie Java 6 jest teraz porównywalne.

  2. jucLock ma różne mechanizmy blokowania. Blokuj przerywalne - próbuj zablokować, dopóki nitka blokująca nie zostanie przerwana; blokada czasowa - spróbuj zablokować na określony czas i poddaj się, jeśli ci się nie uda; tryLock - spróbuj zablokować, jeśli jakiś inny wątek trzyma blokadę, zrezygnuj. Wszystko to jest zawarte oprócz prostej blokady. Blokowanie samoistne zapewnia jedynie proste blokowanie

  3. Styl. Jeśli zarówno 1, jak i 2 nie mieszczą się w kategoriach, którymi zajmujesz się większość ludzi, w tym ja, uważam, że wewnętrzna blokada semenatyki jest łatwiejsza do odczytania i mniej pełna niż blokowanie jucLock.
  4. Wiele warunków. Zablokowany obiekt można powiadomić i poczekać tylko na jedną sprawę. Metoda newCondition Locka pozwala jednemu Lockowi mieć wiele powodów do oczekiwania lub zasygnalizowania. Muszę jeszcze naprawdę potrzebować tej funkcjonalności w praktyce, ale jest to fajna funkcja dla tych, którzy jej potrzebują.
John Vint
źródło
Podobały mi się szczegóły twojego komentarza. Dodałbym jeszcze jeden punkt - ReadWriteLock zapewnia użyteczne zachowanie, jeśli masz do czynienia z kilkoma wątkami, z których tylko niektóre muszą pisać do obiektu. Wiele wątków może jednocześnie odczytywać obiekt i są blokowane tylko wtedy, gdy inny wątek już do niego zapisuje.
Sam Goldberg,
5

Chciałbym dodać jeszcze kilka rzeczy do odpowiedzi Berta F.

Locksobsługują różne metody dokładniejszego sterowania zamkiem, które są bardziej ekspresyjne niż niejawne monitory ( synchronizedzamki)

Blokada zapewnia wyłączny dostęp do współdzielonego zasobu: tylko jeden wątek może uzyskać blokadę na raz, a cały dostęp do zasobu współdzielonego wymaga, aby najpierw uzyskać blokadę. Jednak niektóre blokady mogą umożliwiać równoczesny dostęp do współdzielonego zasobu, na przykład blokada odczytu ReadWriteLock.

Zalety Lock over Synchronization od strony dokumentacji

  1. Zastosowanie zsynchronizowanych metod lub instrukcji zapewnia dostęp do niejawnej blokady monitora związanej z każdym obiektem, ale zmusza do uzyskania i zwolnienia wszystkich blokad w strukturze blokowej

  2. Implementacje blokady zapewniają dodatkową funkcjonalność w stosunku do stosowania zsynchronizowanych metod i instrukcji, zapewniając nieblokującą próbę uzyskania blokady lock (tryLock()), próbę uzyskania blokady, którą można przerwać ( lockInterruptibly()oraz próbę uzyskania blokady, która może timeout (tryLock(long, TimeUnit)).

  3. Klasa Lock może również zapewniać zachowanie i semantykę, które są zupełnie inne niż niejawna blokada monitora, takie jak gwarantowane porządkowanie, użycie bez ponownego wysłania lub wykrycie zakleszczenia

ReentrantLock : Mówiąc prosto, zgodnie z moim rozumieniem, ReentrantLockpozwala obiektowi ponownie wejść z jednej sekcji krytycznej do innej sekcji krytycznej. Ponieważ masz już blokadę umożliwiającą wejście do jednej sekcji krytycznej, możesz użyć innej sekcji krytycznej na tym samym obiekcie, używając bieżącej blokady.

ReentrantLockkluczowe funkcje zgodnie z tym artykułem

  1. Możliwość blokowania przerywanego.
  2. Możliwość przekroczenia limitu czasu podczas oczekiwania na blokadę.
  3. Moc do stworzenia sprawiedliwego zamka.
  4. API, aby uzyskać listę wątków oczekujących na blokadę.
  5. Elastyczność w próbie blokady bez blokowania.

Możesz użyć ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLockdo dalszego uzyskania kontroli nad szczegółowym blokowaniem operacji odczytu i zapisu.

Oprócz tych trzech ReentrantLocks java 8 zapewnia jeszcze jedną blokadę

StampedLock:

Java 8 jest dostarczana z nowym rodzajem blokady o nazwie StampedLock, który obsługuje również blokady odczytu i zapisu, tak jak w powyższym przykładzie. W przeciwieństwie do ReadWriteLock metody blokowania StampedLock zwracają znaczek reprezentowany przez długą wartość.

Możesz użyć tych znaczków, aby zwolnić blokadę lub sprawdzić, czy blokada jest nadal ważna. Dodatkowo wybite zamki obsługują inny tryb blokady zwany blokowaniem optymistycznym.

Zapoznaj się z tym artykułem na temat używania różnych rodzajów ReentrantLocki StampedLockzamków.

Ravindra babu
źródło
4

Główną różnicą jest sprawiedliwość, innymi słowy, czy prośby są rozpatrywane w FIFO, czy może istnieć barka? Synchronizacja na poziomie metody zapewnia sprawiedliwy lub FIFO podział zamka. Za pomocą

synchronized(foo) {
}

lub

lock.acquire(); .....lock.release();

nie zapewnia uczciwości.

Jeśli masz spory o blokadę, możesz łatwo natknąć się na barki, w których nowsze żądania blokują się, a starsze blokują się. Widziałem przypadki, w których 200 wątków przychodzi w krótkiej kolejności do blokady, a drugi, który przybywa, jest przetwarzany jako ostatni. Jest to odpowiednie dla niektórych aplikacji, ale dla innych jest zabójcze.

Pełna książka na ten temat znajduje się w książce Briana Goetza „Java Concurrency In Practice”, rozdział 13.3.

Brian Tarbox
źródło
5
„Synchronizacja na poziomie metody zapewnia sprawiedliwy lub FIFO podział zamka.” => Naprawdę? Czy mówisz, że zsynchronizowana metoda zachowuje się inaczej w stosunku do uczciwości niż zawijanie zawartości metod do zsynchronizowanego bloku {}? Nie sądzę, czy też źle zrozumiałem to zdanie ...?
weiresr
Tak, choć zaskakujące i sprzeczne z intuicją, to prawda. Książka Goetza jest najlepszym wytłumaczeniem.
Brian Tarbox
Jeśli spojrzysz na kod dostarczony przez @BrianTarbox, zsynchronizowany blok używa jakiegoś obiektu innego niż „to” do blokowania. Teoretycznie nie ma różnicy między metodą zsynchronizowaną a umieszczeniem całego ciała tej metody w zsynchronizowanym bloku, o ile blok używa „tego” jako blokady.
xburgos,
Odpowiedź powinna zostać zredagowana w celu dołączenia cytatu i wyraźnego „zapewnienia”, że jest to „gwarancja statystyczna”, a nie deterministyczna.
Nathan Hughes,
Przepraszam, właśnie odkryłem, że omyłkowo głosowałem za odrzuceniem tej odpowiedzi kilka dni temu (niezdarne kliknięcie). Niestety SO obecnie nie pozwala mi go przywrócić. Mam nadzieję, że mogę to naprawić później.
Mikko Östlund
3

Książka Briana Goetza „Java Concurrency In Practice”, sekcja 13.3: „... Podobnie jak domyślna ReentrantLock, blokowanie wewnętrzne nie oferuje żadnych deterministycznych gwarancji uczciwości, ale gwarancje uczciwości statystycznej większości implementacji blokowania są wystarczające dla prawie wszystkich sytuacji ...”

bodrin
źródło
2

Blokada ułatwia życie programistom. Oto kilka sytuacji, które można łatwo osiągnąć za pomocą zamka.

  1. Zablokuj jedną metodą i zwolnij blokadę inną metodą.
  2. Jeśli jednak masz dwa wątki działające na dwóch różnych fragmentach kodu, to w pierwszym wątku jest wymagana pewna część kodu w drugim wątku (podczas gdy niektóre inne wątki również działają na tym samym fragmencie kodu w drugim wątek jednocześnie). Blokada dzielona może dość łatwo rozwiązać ten problem.
  3. Wdrażanie monitorów. Na przykład prosta kolejka, w której metody put i get są wykonywane z wielu innych wątków. Jednak nie chcesz, aby wiele metod put (lub get) działało jednocześnie, ani metoda put i get działały jednocześnie. Prywatna blokada znacznie ułatwi Ci to życie.

Natomiast blokada i warunki są oparte na zsynchronizowanym mechanizmie. Dlatego z pewnością można osiągnąć tę samą funkcjonalność, co przy użyciu zamka. Jednak rozwiązywanie złożonych scenariuszy zsynchronizowanych może utrudnić ci życie i może odejść od rozwiązania rzeczywistego problemu.

Md Monjur Ul Hasan
źródło
1

Główna różnica między blokadą a synchronizacją:

  • dzięki blokadom możesz odblokowywać i uzyskiwać blokady w dowolnej kolejności.
  • zsynchronizowany, możesz zwolnić blokady tylko w kolejności, w jakiej zostały nabyte.
NKumar
źródło
0

Blokowanie i synchronizowanie bloku służy temu samemu celowi, ale zależy to od użycia. Rozważ poniższą część

void randomFunction(){
.
.
.
synchronize(this){
//do some functionality
}

.
.
.
synchronize(this)
{
// do some functionality
}


} // end of randomFunction

W powyższym przypadku, jeśli wątek wejdzie do bloku synchronizacji, drugi blok również zostanie zablokowany. Jeśli istnieje wiele takich bloków synchronizacji na tym samym obiekcie, wszystkie bloki są zablokowane. W takich sytuacjach można użyć java.util.concurrent.Lock, aby zapobiec niepożądanemu blokowaniu bloków

Shyam
źródło