Po co używać ReentrantLock, jeśli można korzystać z synchronizacji (tego)?

317

Próbuję zrozumieć, co sprawia, że ​​blokada współbieżności jest tak ważna, jeśli można jej użyć synchronized (this). W poniższym kodzie atrapy mogę wykonać:

  1. zsynchronizował całą metodę lub zsynchronizował wrażliwy obszar ( synchronized(this){...})
  2. LUB zablokuj wrażliwy obszar kodu za pomocą ReentrantLock.

Kod:

    private final ReentrantLock lock = new ReentrantLock(); 
    private static List<Integer> ints;

    public Integer getResult(String name) { 
        .
        .
        .
        lock.lock();
        try {
            if (ints.size()==3) {
                ints=null;
                return -9;
            }   

            for (int x=0; x<ints.size(); x++) {
                System.out.println("["+name+"] "+x+"/"+ints.size()+". values >>>>"+ints.get(x));
            }

        } finally {
            lock.unlock();
        } 
        return random;
}
adhg
źródło
1
BTW, wszystkie wewnętrzne blokady Java są ponownie wydawane z natury.
Aniket Thakur,
@pongapundit, więc synchronized(this){synchronized(this){//some code}}nie spowoduje martwej blokady. W przypadku wewnętrznej blokady, jeśli uzyskają monitor na zasobie i jeśli chcą go ponownie, mogą go uzyskać bez martwej blokady.
Aniket Thakur

Odpowiedzi:

474

ReentrantLock jest nieuporządkowane , w przeciwieństwie do synchronizedkonstrukcji - to znaczy, że nie trzeba używać strukturę blokową do blokowania, a nawet może posiadać blokadę całej metody. Przykład:

private ReentrantLock lock;

public void foo() {
  ...
  lock.lock();
  ...
}

public void bar() {
  ...
  lock.unlock();
  ...
}

Taki przepływ jest niemożliwy do przedstawienia za pomocą jednego monitora w synchronizedkonstrukcji.


Oprócz tego ReentrantLockobsługuje odpytywanie blokady i blokada przerywana czeka na obsługę limitu czasu . ReentrantLockobsługuje także konfigurowalną politykę uczciwości , umożliwiając bardziej elastyczne planowanie wątków.

Konstruktor dla tej klasy akceptuje opcjonalny parametr uczciwości . Po ustawieniu true, w sporze, zamki sprzyjają zapewnieniu dostępu do najdłużej czekającego wątku. W przeciwnym razie ta blokada nie gwarantuje żadnego konkretnego zamówienia dostępu. Programy korzystające z uczciwych blokad, do których dostęp ma wiele wątków, mogą wykazywać niższą ogólną przepustowość (tj. Są wolniejsze; często znacznie wolniejsze) niż programy korzystające z ustawień domyślnych, ale mają mniejsze rozbieżności w czasie w celu uzyskania blokad i zagwarantowania braku głodu. Należy jednak pamiętać, że uczciwość zamków nie gwarantuje rzetelności planowania wątków. Zatem jeden z wielu wątków korzystających z uczciwego zamka może uzyskać go wiele razy z rzędu, podczas gdy inne aktywne wątki nie postępują i obecnie nie utrzymują zamka. Należy również pamiętać, że nieterminowetryLockmetoda nie uwzględnia ustawienia uczciwości. Pomyślnie, jeśli blokada jest dostępna, nawet jeśli czekają inne wątki.


ReentrantLock może być również bardziej skalowalny , działając znacznie lepiej przy większej rywalizacji. Możesz przeczytać więcej na ten temat tutaj .

Twierdzenie to zostało jednak zakwestionowane; zobacz następujący komentarz:

W teście blokady ponownego wysyłania nowa blokada jest tworzona za każdym razem, dlatego nie ma blokady wyłącznej, a dane wynikowe są nieprawidłowe. Ponadto łącze IBM nie oferuje kodu źródłowego dla bazowego testu porównawczego, więc niemożliwe jest określenie, czy test został przeprowadzony poprawnie.


Kiedy należy użyć ReentrantLocks? Zgodnie z tym artykułem developerWorks ...

Odpowiedź jest dość prosta - użyj jej, gdy rzeczywiście potrzebujesz czegoś, co nie zapewnia synchronized, na przykład oczekiwania na czas z blokadą, oczekiwania na blokadę przerywaną, blokady niestrukturalne, wiele zmiennych warunkowych lub odpytywanie blokad. ReentrantLockma również zalety skalowalności i należy go użyć, jeśli rzeczywiście występuje sytuacja, w której występuje duża rywalizacja, ale należy pamiętać, że zdecydowana większość synchronizedbloków prawie nigdy nie wykazuje żadnej rywalizacji, a tym bardziej wysokiej rywalizacji. Radziłbym rozwijać z synchronizacją, dopóki synchronizacja nie okaże się nieodpowiednia, zamiast zakładać, że „wydajność będzie lepsza”, jeśli użyjeszReentrantLock. Pamiętaj, że są to zaawansowane narzędzia dla zaawansowanych użytkowników. (Naprawdę zaawansowani użytkownicy preferują najprostsze narzędzia, jakie mogą znaleźć, dopóki nie przekonają się, że proste narzędzia są nieodpowiednie). Jak zawsze, najpierw popraw to, a potem martw się, czy musisz to zrobić szybciej.

oldrinb
źródło
26
Link „znany jako bardziej skalowalny” do lycog.com powinien zostać usunięty. W teście ponownej blokady tworzona jest za każdym razem nowa blokada, dlatego nie ma blokady wyłącznej, a dane wynikowe są nieprawidłowe. Ponadto łącze IBM nie oferuje kodu źródłowego dla bazowego testu porównawczego, więc nie można scharakteryzować, czy test został przeprowadzony poprawnie. Osobiście po prostu usunę całą linię dotyczącą skalowalności, ponieważ całe twierdzenie jest w zasadzie nieobsługiwane.
Dev
2
Zmodyfikowałem post w świetle Twojej odpowiedzi.
oldrinb
6
Jeśli wydajność jest dla Ciebie bardzo ważna, nie zapomnij poszukać sposobu, w którym wcale NIE potrzebujesz synchronizacji.
mcoolive,
2
Wydajność nie ma dla mnie żadnego sensu. Jeśli blokada reantranta działałaby lepiej, to dlaczego synchronizacja nie byłaby realizowana nie tylko w taki sam sposób, jak blokada reantranta?
tObi
2
@ user2761895 ReentrantLockPseudoRandomkod w linku Lycog używa zupełnie nowych, niezamówionych blokad przy każdym wywołaniu setSeedinext
oldrinb
14

ReentrantReadWriteLockjest zamkiem specjalistycznym, natomiast synchronized(this)jest zamkiem ogólnego zastosowania. Są podobne, ale nie do końca takie same.

Masz rację, że możesz użyć synchronized(this)zamiast, ReentrantReadWriteLockale nie zawsze jest odwrotnie.

Jeśli chcesz lepiej zrozumieć, co sprawia, że ReentrantReadWriteLockspecjalne wyszukiwanie zawiera informacje na temat synchronizacji wątków producent-konsument.

Zasadniczo pamiętasz, że synchronizacja całymi metodami i synchronizacja ogólnego zastosowania (przy użyciu synchronizedsłowa kluczowego) mogą być używane w większości aplikacji bez zbytniego zastanawiania się nad semantyką synchronizacji, ale jeśli chcesz wycisnąć wydajność z kodu, może być konieczne poznaj inne, bardziej precyzyjne lub specjalne mechanizmy synchronizacji.

Nawiasem mówiąc, używanie synchronized(this)- i ogólnie blokowanie za pomocą instancji klasy publicznej - może być problematyczne, ponieważ otwiera twój kod na potencjalne martwe blokady, ponieważ ktoś inny nieświadomie może spróbować zablokować twój obiekt gdzieś indziej w programie.

Mike Dinescu
źródło
aby zapobiec potencjalnym zakleszczeniom, ponieważ ktoś nieświadomie może spróbować zablokować twój obiekt gdzieś indziej w grze, użyj prywatnej instancji obiektu jako synchronizacji Monitoruj w ten sposób: public class MyLock { private final Object protectedLongLockingMonitor = new Object(); private long protectedLong = 0L; public void incrementProtectedLong() { synchronized(protectedLongLockingMonitor) { protectedLong++; } } }
sushicutta
9

Ze strony dokumentacji Oracle o ReentrantLock :

Powtarzające się wzajemne wykluczenie Blokada z takim samym podstawowym zachowaniem i semantyką jak niejawna blokada monitora, do której dostęp uzyskano przy użyciu zsynchronizowanych metod i instrukcji, ale z rozszerzonymi możliwościami.

  1. ReentrantLock jest własnością wątku ostatnia skutecznie blokujący, ale jeszcze nie odblokowania. Blokada wywołująca wątek powróci, po uzyskaniu blokady, gdy zamek nie jest własnością innego wątku. Metoda zwróci natychmiast, jeśli bieżący wątek jest już właścicielem blokady.

  2. Konstruktor dla tej klasy akceptuje opcjonalny parametr uczciwości . Po ustawieniu wartości true, wbrew sprzeczkom, blokady sprzyjają przyznawaniu dostępu do najdłużej czekającego wątku . W przeciwnym razie ta blokada nie gwarantuje żadnego konkretnego zamówienia dostępu.

Najważniejsze funkcje ReentrantLock 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.WriteLock, aby dalej uzyskać kontrolę nad szczegółowym blokowaniem operacji odczytu i zapisu.

Zapoznaj się z tym artykułem autorstwa Benjamana na temat używania różnego rodzaju blokad ReentrantLocks

Ravindra babu
źródło
2

Możesz użyć blokad ponownych wysyłek z polityką uczciwości lub limitem czasu, aby uniknąć głodu gwintu. Możesz zastosować zasadę uczciwości wątków. Pomoże to uniknąć ciągłego czekania na dostęp do zasobów.

private final ReentrantLock lock = new ReentrantLock(true);
//the param true turns on the fairness policy. 

„Polityka uczciwości” wybiera następny uruchamialny wątek do wykonania. Opiera się na priorytecie, czas od ostatniego uruchomienia, bla bla

Synchronizacja może również blokować w nieskończoność, jeśli nie można uciec z bloku. Reentrantlock może mieć ustawiony limit czasu.

j2emanue
źródło
1

Zsynchronizowane blokady nie oferują żadnego mechanizmu oczekiwania, w którym po wykonaniu jednego wątku dowolny wątek działający równolegle może uzyskać blokadę. Dzięki temu wątek znajdujący się w systemie i działający przez dłuższy czas nigdy nie ma szans na dostęp do udostępnionego zasobu, co prowadzi do głodu.

Blokady ponownego wysyłania są bardzo elastyczne i mają politykę uczciwości, w której jeśli wątek czeka przez dłuższy czas, a po zakończeniu aktualnie wykonywanego wątku możemy upewnić się, że dłuższy oczekujący wątek ma szansę na dostęp do udostępnionego zasobu, zmniejszając się w ten sposób przepustowość systemu i zwiększenie jego czasochłonności.

Sumit Kapoor
źródło
1

Załóżmy, że ten kod działa w wątku:

private static ReentrantLock lock = new ReentrantLock();

void accessResource() {
    lock.lock();
    if( checkSomeCondition() ) {
        accessResource();
    }
    lock.unlock();
}

Ponieważ wątek posiada blokadę, pozwoli na wielokrotne wywołanie blokady (), więc ponownie wejdzie w blokadę. Można to osiągnąć za pomocą liczby referencyjnej, aby nie trzeba było ponownie uzyskiwać blokady.

RonTLV
źródło
0

Należy pamiętać, że:

nazwa „ ReentrantLock ” podaje niewłaściwy komunikat o innym mechanizmie blokującym, że nie są ponownie włączani. To nie jest prawda. Blokada uzyskana przez „zsynchronizowany” jest również ponownie uruchamiana w Javie.

Kluczowa różnica polega na tym, że „zsynchronizowany” korzysta z wewnętrznej blokady (takiej, którą posiada każdy obiekt), podczas gdy interfejs API blokady nie.

Lato
źródło
0

Myślę, że metody wait / powiadom / powiadom Wszystkie metody nie należą do klasy Object, ponieważ zanieczyszczają wszystkie obiekty metodami rzadko używanymi. Mają znacznie większy sens na dedykowanej klasie Lock. Z tego punktu widzenia być może lepiej jest użyć narzędzia specjalnie zaprojektowanego do danego zadania - tj. ReentrantLock.

Solubris
źródło