Czy istnieje korzyść z zastosowania metody synchronicznej zamiast synchronizowanego bloku?


401

Czy ktoś może mi powiedzieć przewagę metody zsynchronizowanej nad zsynchronizowanym blokiem z przykładem?




1
@cletus to pytanie jest zupełnie inne niż stackoverflow.com/questions/442564/…
Yukio Fukuzawa

Odpowiedzi:


431

Czy ktoś może mi powiedzieć na przykład przewagę metody zsynchronizowanej nad synchronizowanym blokiem? Dzięki.

Nie ma wyraźnej przewagi przy użyciu metody synchronicznej nad blokiem.

Być może jedynym (ale nie nazwałbym tego zaletą) jest to, że nie musisz dołączać odwołania do obiektu this.

Metoda:

public synchronized void method() { // blocks "this" from here.... 
    ...
    ...
    ...
} // to here

Blok:

public void method() { 
    synchronized( this ) { // blocks "this" from here .... 
        ....
        ....
        ....
    }  // to here...
}

Widzieć? Żadnej przewagi.

Bloki nie mają przewagę nad metodami choć przeważnie elastyczność, ponieważ można użyć innego obiektu jako zamka podczas synchronizowania metody by zablokować cały obiekt.

Porównać:

// locks the whole object
... 
private synchronized void someInputRelatedWork() {
    ... 
}
private synchronized void someOutputRelatedWork() {
    ... 
}

vs.

// Using specific locks
Object inputLock = new Object();
Object outputLock = new Object();

private void someInputRelatedWork() {
    synchronized(inputLock) { 
        ... 
    } 
}
private void someOutputRelatedWork() {
    synchronized(outputLock) { 
        ... 
    }
}

Ponadto, jeśli metoda rośnie, nadal możesz rozdzielić zsynchronizowaną sekcję:

 private void method() {
     ... code here
     ... code here
     ... code here
    synchronized( lock ) { 
        ... very few lines of code here
    }
     ... code here
     ... code here
     ... code here
     ... code here
}

44
Korzyścią dla konsumenta interfejsu API jest to, że użycie zsynchronizowanego słowa kluczowego w deklaracji metody również jawnie deklaruje, że metoda synchronizuje się z instancją obiektu i jest (prawdopodobnie) bezpieczna dla wątków.
Scrubbie

59
Wiem, że to stare pytanie, ale synchronizacja „tego” jest uważana w niektórych kręgach za anty-wzór. Niezamierzoną konsekwencją jest to, że poza klasą ktoś może zablokować odwołanie do obiektu, które jest równe „to”, i uniemożliwić innym wątkom przekroczenie barier w klasie, potencjalnie tworząc sytuację zakleszczenia. Tworzenie „prywatnego obiektu końcowego = nowy obiekt ();” zmienna służąca wyłącznie do blokowania jest często stosowanym rozwiązaniem. Oto kolejne pytanie dotyczące bezpośrednio tego problemu.
justin.hughey

30
„podczas gdy synchronizacja metody zablokowałaby całą klasę.” To nie jest poprawne. Nie blokuje całej klasy, ale całą instancję. Wiele obiektów z tej samej klasy posiada własny zamek. :) Pozdrawiam
codepleb

4
Ciekawe w tym jest to, że użycie metody zsynchronizowanej spowoduje, że wygenerowany kod bajtowy będzie miał 1 mniej instrukcji, ponieważ metody mają zsynchronizowany bit zapisany w ich sygnaturze. Ponieważ długość kodu bajtowego jest czynnikiem decydującym o tym, czy metoda zostanie wstawiona, przeniesienie bloku do sygnatury metody może być różnicą w podejmowaniu decyzji. Teoretycznie zresztą. Nie oparłbym decyzji projektowej na zapisaniu pojedynczej instrukcji kodu bajtowego, co wydaje się strasznym pomysłem. Ale wciąż jest różnica. =)
corsiKa

2
@corsiKa: zapisujesz więcej niż jedną instrukcję. synchronizedBlok jest realizowany za pomocą dwóch instrukcji, monitorenteri monitorexit, oraz obsługę wyjątków, które gwarantuje, że monitorexitnazywa się nawet w wyjątkowych przypadkach. To wszystko jest zapisywane podczas korzystania z synchronizedmetody.
Holger

139

Jedyną prawdziwą różnicą jest to, że zsynchronizowany blok może wybrać obiekt, na którym ma być synchronizowany. Zsynchronizowana metoda może używać tylko 'this'(lub odpowiedniej instancji klasy dla zsynchronizowanej metody klasy). Na przykład są one semantycznie równoważne:

synchronized void foo() {
  ...
}

void foo() {
    synchronized (this) {
      ...
    }
}

Ten ostatni jest bardziej elastyczny, ponieważ może konkurować o skojarzoną blokadę dowolnego obiektu, często zmiennej elementu. Jest to również bardziej szczegółowe, ponieważ możesz mieć współbieżny kod wykonywany przed i po bloku, ale nadal w ramach metody. Oczywiście równie łatwo można zastosować metodę zsynchronizowaną, przekształcając współbieżny kod w osobne niezsynchronizowane metody. Użyj tego, co czyni kod bardziej zrozumiałym.


Ta ostatnia może również mieć zaletę, jeśli nie cały kod w foo () wymaga synchronizacji.
Evan

1
To prawda, ale nie to, o co pytał „Warrior”: „Zaletą metody synchronicznej” nie ma.
OscarRyz

76

Metoda synchroniczna

Plusy:

  • Twoje IDE może wskazywać zsynchronizowane metody.
  • Składnia jest bardziej zwarta.
  • Wymusza podział zsynchronizowanych bloków na oddzielne metody.

Cons:

  • Synchronizuje się z tym, dzięki czemu osoby z zewnątrz mogą się z nim synchronizować.
  • Trudniej jest przenieść kod poza zsynchronizowany blok.

Zsynchronizowany blok

Plusy:

  • Pozwala na użycie zmiennej prywatnej dla zamka i zmusza zamek do pozostania wewnątrz klasy.
  • Zsynchronizowane bloki można znaleźć, wyszukując odniesienia do zmiennej.

Cons:

  • Składnia jest bardziej skomplikowana, przez co kod jest trudniejszy do odczytania.

Osobiście wolę używać metod zsynchronizowanych z klasami skoncentrowanymi tylko na rzeczach wymagających synchronizacji. Taka klasa powinna być tak mała, jak to możliwe, a więc powinna być łatwa przegląd synchronizacji. Inni nie powinni się przejmować synchronizacją.


Kiedy mówisz „pozostań w klasie”, masz na myśli „pozostań w obiekcie ”, czy coś mi brakuje?
OldPeculier

36

Główna różnica polega na tym, że jeśli korzystasz ze zsynchronizowanego bloku, możesz zablokować obiekt inny niż ten co pozwala być bardziej elastycznym.

Załóżmy, że masz kolejkę komunikatów oraz wielu producentów i konsumentów wiadomości. Nie chcemy, aby producenci przeszkadzali sobie nawzajem, ale konsumenci powinni mieć możliwość wyszukiwania wiadomości bez konieczności oczekiwania na producentów. Więc po prostu tworzymy obiekt

Object writeLock = new Object();

I odtąd za każdym razem, gdy producent chce dodać nową wiadomość, po prostu to blokujemy:

synchronized(writeLock){
  // do something
}

Tak więc konsumenci mogą nadal czytać, a producenci zostaną zablokowani.


2
Twój przykład ogranicza się do nieniszczących odczytów. Jeśli odczyt usunie komunikat z kolejki, nie powiedzie się, jeśli zostanie to zrobione w pewnym momencie, gdy producent zapisuje w kolejce.
ceving

30

Metoda zsynchronizowana

Zsynchronizowane metody mają dwa efekty.
Po pierwsze, gdy jeden wątek wykonuje zsynchronizowaną metodę dla obiektu, wszystkie inne wątki, które wywołują metody zsynchronizowane dla tego samego bloku obiektu (zawieszają wykonywanie), dopóki pierwszy wątek nie zostanie zakończony z obiektem.

Po drugie, kiedy zsynchronizowana metoda kończy działanie, automatycznie ustanawia relację przed zdarzeniem z każdym kolejnym wywołaniem zsynchronizowanej metody dla tego samego obiektu. Gwarantuje to, że zmiany stanu obiektu są widoczne dla wszystkich wątków.

Pamiętaj, że konstruktory nie mogą być synchronizowane - użycie słowa kluczowego synchronizowanego z konstruktorem jest błędem składni. Synchronizacja konstruktorów nie ma sensu, ponieważ tylko wątek, który tworzy obiekt, powinien mieć do niego dostęp podczas jego konstruowania.

Instrukcja zsynchronizowana

W przeciwieństwie do metod zsynchronizowanych, instrukcje zsynchronizowane muszą określać obiekt, który zapewnia wewnętrzną blokadę: Najczęściej używam tego do synchronizowania dostępu do listy lub mapy, ale nie chcę blokować dostępu do wszystkich metod obiektu.

P: Wewnętrzne blokady i synchronizacja Synchronizacja opiera się na wewnętrznej jednostce znanej jako wewnętrzna blokada lub blokada monitora. (Specyfikacja API często odnosi się do tego bytu po prostu jako „monitor”). Zamki wewnętrzne odgrywają rolę w obu aspektach synchronizacji: wymuszając wyłączny dostęp do stanu obiektu i ustanawiając relacje, które są istotne dla widoczności.

Z każdym obiektem jest związana wewnętrzna blokada. Zgodnie z konwencją wątek, który wymaga wyłącznego i spójnego dostępu do pól obiektu, musi uzyskać wewnętrzną blokadę obiektu przed uzyskaniem do nich dostępu, a następnie zwolnić blokadę wewnętrzną, gdy jest to zrobione. Mówi się, że wątek jest właścicielem wewnętrznego zamka między momentem, w którym zamek uzyskał i zwolnił zamek. Tak długo, jak wątek posiada wewnętrzny zamek, żaden inny wątek nie może uzyskać tego samego zamka. Drugi wątek zostanie zablokowany podczas próby uzyskania blokady.

package test;

public class SynchTest implements Runnable {  
    private int c = 0;

    public static void main(String[] args) {
        new SynchTest().test();
    }

    public void test() {
        // Create the object with the run() method
        Runnable runnable = new SynchTest();
        Runnable runnable2 = new SynchTest();
        // Create the thread supplying it with the runnable object
        Thread thread = new Thread(runnable,"thread-1");
        Thread thread2 = new Thread(runnable,"thread-2");
//      Here the key point is passing same object, if you pass runnable2 for thread2,
//      then its not applicable for synchronization test and that wont give expected
//      output Synchronization method means "it is not possible for two invocations
//      of synchronized methods on the same object to interleave"

        // Start the thread
        thread.start();
        thread2.start();
    }

    public synchronized  void increment() {
        System.out.println("Begin thread " + Thread.currentThread().getName());
        System.out.println(this.hashCode() + "Value of C = " + c);
//      If we uncomment this for synchronized block, then the result would be different
//      synchronized(this) {
            for (int i = 0; i < 9999999; i++) {
                c += i;
            }
//      }
        System.out.println("End thread " + Thread.currentThread().getName());
    }

//    public synchronized void decrement() {
//        System.out.println("Decrement " + Thread.currentThread().getName());
//    }

    public int value() {
        return c;
    }

    @Override
    public void run() {
        this.increment();
    }
}

Sprawdź krzyżowo różne wyjścia metodą zsynchronizowaną, blokuj i bez synchronizacji.


10
+1 za to, że jako jedyny wspomniał, że konstruktorów nie można zsynchronizować . Oznacza to, że w konstruktorze naprawdę masz tylko jedną opcję: Zsynchronizowane bloki.
ef2011

Przetestowałem twój kod zgodnie z zaleceniami, ale C ma zawsze wartość 0, następnie -2024260031 i jedyną rzeczą, która go zmienia, jest kod skrótu. Jakie zachowanie powinno być widoczne?
Justin Johnson

Powinieneś zacytować poniższe artykuły, z których treść została dostarczona: docs.oracle.com/javase/tutorial/essential/concurrency/... i docs.oracle.com/javase/tutorial/essential/concurrency/…
Ravindra babu

29

Uwaga: statyczne metody synchronizacji i bloki działają na obiekcie klasy.

public class MyClass {
   // locks MyClass.class
   public static synchronized void foo() {
// do something
   }

   // similar
   public static void foo() {
      synchronized(MyClass.class) {
// do something
      }
   }
}

18

Kiedy kompilator Java konwertuje kod źródłowy na kod bajtowy, bardzo zróznicuje metody synchronizowane i bloki zsynchronizowane.

Gdy JVM wykonuje metodę zsynchronizowaną, wątek wykonujący stwierdza, że ​​struktura metody method_info metody ma ustawioną flagę ACC_SYNCHRONIZED, a następnie automatycznie uzyskuje blokadę obiektu, wywołuje metodę i zwalnia blokadę. Jeśli wystąpi wyjątek, nić automatycznie zwalnia blokadę.

Z drugiej strony synchronizacja bloku metod pomija wbudowaną obsługę JVM w zakresie uzyskiwania blokady obiektu i obsługi wyjątków i wymaga jawnego zapisania tej funkcji w kodzie bajtowym. Jeśli przeczytasz kod bajtu dla metody ze zsynchronizowanym blokiem, zobaczysz kilkanaście dodatkowych operacji w celu zarządzania tą funkcjonalnością.

To pokazuje wywołania w celu wygenerowania zarówno metody zsynchronizowanej, jak i zsynchronizowanego bloku:

public class SynchronizationExample {
    private int i;

    public synchronized int synchronizedMethodGet() {
        return i;
    }

    public int synchronizedBlockGet() {
        synchronized( this ) {
            return i;
        }
    }
}

synchronizedMethodGet()Sposób wytwarza się następujące kod binarny:

0:  aload_0
1:  getfield
2:  nop
3:  iconst_m1
4:  ireturn

A oto kod bajtu z synchronizedBlockGet()metody:

0:  aload_0
1:  dup
2:  astore_1
3:  monitorenter
4:  aload_0
5:  getfield
6:  nop
7:  iconst_m1
8:  aload_1
9:  monitorexit
10: ireturn
11: astore_2
12: aload_1
13: monitorexit
14: aload_2
15: athrow

Jedną znaczącą różnicą między metodą zsynchronizowaną a blokiem jest to, że Zsynchronizowany blok ogólnie zmniejsza zakres blokady. Ponieważ zakres blokady jest odwrotnie proporcjonalny do wydajności, zawsze lepiej jest zablokować tylko krytyczną sekcję kodu. Jednym z najlepszych przykładów użycia bloku synchronicznego jest podwójne sprawdzanie blokowania we wzorze Singleton, gdzie zamiast blokowania całej getInstance()metody blokujemy tylko krytyczną sekcję kodu, która jest używana do utworzenia instancji Singleton. Znacząco poprawia to wydajność, ponieważ blokowanie jest wymagane tylko jeden lub dwa razy.

Podczas korzystania z metod zsynchronizowanych należy zachować szczególną ostrożność, jeśli łączysz zarówno metody zsynchronizowane statycznie, jak i niestatyczne.


1
Jeśli spojrzymy na zsynchronizowaną metodę bytecode, bytecode jest bardziej zwarty i prosty, więc dlaczego nie jest tak szybszy, że synchronizowany blok?
eatSleepCode

@eatSleepCode Uwaga: jest to kod bajtowy, który jest dalej „kompilowany” przez JVM. JVM doda niezbędne monitorenteri monitorexitprzed uruchomieniem kodu.
Philip Couling,

12

Najczęściej używam tego do synchronizacji dostępu do listy lub mapy, ale nie chcę blokować dostępu do wszystkich metod obiektu.

W poniższym kodzie jeden wątek modyfikujący listę nie będzie blokował oczekiwania na wątek modyfikujący mapę. Jeśli metody zostały zsynchronizowane z obiektem, każda metoda musiałaby poczekać, nawet jeśli wprowadzane modyfikacje nie kolidowałyby.

private List<Foo> myList = new ArrayList<Foo>();
private Map<String,Bar) myMap = new HashMap<String,Bar>();

public void put( String s, Bar b ) {
  synchronized( myMap ) {
    myMap.put( s,b );
    // then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public void hasKey( String s, ) {
  synchronized( myMap ) {
    myMap.hasKey( s );
  }
}

public void add( Foo f ) {
  synchronized( myList ) {
    myList.add( f );
// then some thing that may take a while like a database access or RPC or notifying listeners
  }
}

public Thing getMedianFoo() {
  Foo med = null;
  synchronized( myList ) {
    Collections.sort(myList);
    med = myList.get(myList.size()/2); 
  }
  return med;
}

7

Dzięki zsynchronizowanym blokom możesz mieć wiele synchronizatorów, dzięki czemu może jednocześnie działać wiele jednoczesnych, ale nie powodujących konfliktu rzeczy.


6

Zsynchronizowane metody można sprawdzić za pomocą refleksyjnego interfejsu API. Może to być przydatne do testowania niektórych umów, takich jak synchronizacja wszystkich metod w modelu .

Poniższy fragment wypisuje wszystkie zsynchronizowane metody Hashtable:

for (Method m : Hashtable.class.getMethods()) {
        if (Modifier.isSynchronized(m.getModifiers())) {
            System.out.println(m);
        }
}

5

Ważna uwaga na temat korzystania z synchronizowanego bloku: uważaj, czego używasz jako obiektu blokady!

Fragment kodu z powyższego user2277816 ilustruje ten punkt, w którym odwołanie do literału łańcucha jest używane jako obiekt blokujący. Zdaj sobie sprawę z tego, że literały łańcuchowe są automatycznie internowane w Javie i powinieneś zacząć dostrzegać problem: każdy fragment kodu, który synchronizuje się na dosłownie „blokadzie”, ma tę samą blokadę! Może to łatwo doprowadzić do impasu przy całkowicie niepowiązanych fragmentach kodu.

Nie chodzi tylko o obiekty String, na które trzeba uważać. Prymitywy w pudełkach są również niebezpieczne, ponieważ metody autoboxowania i metody valueOf mogą ponownie wykorzystywać te same obiekty, w zależności od wartości.

Aby uzyskać więcej informacji, zobacz: https://www.securecoding.cert.org/confluence/display/java/LCK01-J.+Do+not+synchronize+on+objects+that+may+be+reused


5

Często używanie blokady na poziomie metody jest zbyt niegrzeczne. Po co blokować fragment kodu, który nie ma dostępu do żadnych udostępnionych zasobów, blokując całą metodę. Ponieważ każdy obiekt ma blokadę, można tworzyć fikcyjne obiekty w celu wdrożenia synchronizacji na poziomie bloku. Poziom bloku jest bardziej wydajny, ponieważ nie blokuje całej metody.

Oto przykład

Poziom metody

class MethodLevel {

  //shared among threads
SharedResource x, y ;

public void synchronized method1() {
   //multiple threads can't access
}
public void synchronized method2() {
  //multiple threads can't access
}

 public void method3() {
  //not synchronized
  //multiple threads can access
 }
}

Poziom bloku

class BlockLevel {
  //shared among threads
  SharedResource x, y ;

  //dummy objects for locking
  Object xLock = new Object();
  Object yLock = new Object();

    public void method1() {
     synchronized(xLock){
    //access x here. thread safe
    }

    //do something here but don't use SharedResource x, y
    // because will not be thread-safe
     synchronized(xLock) {
       synchronized(yLock) {
      //access x,y here. thread safe
      }
     }

     //do something here but don't use SharedResource x, y
     //because will not be thread-safe
    }//end of method1
 }

[Edytować]

Dla Collectionjak Vectori Hashtableoni są zsynchronizowane, gdy ArrayListalbo HashMapnie są i muszą Państwo ustawić zsynchronizowane słowa kluczowego lub invoke Collections zsynchronizowany sposób:

Map myMap = Collections.synchronizedMap (myMap); // single lock for the entire map
List myList = Collections.synchronizedList (myList); // single lock for the entire list

5

Jedyna różnica: zsynchronizowane bloki pozwalają na precyzyjne blokowanie w przeciwieństwie do metody zsynchronizowanej

Gruntownie synchronized blok lub metody do pisania bezpiecznego kodu wątku, unikając błędów niespójności pamięci.

To pytanie jest bardzo stare i wiele rzeczy zostało zmienionych w ciągu ostatnich 7 lat. Wprowadzono nowe konstrukcje programistyczne dla bezpieczeństwa wątków.

Bezpieczeństwo wątków można osiągnąć, używając zaawansowanego interfejsu API współbieżności zamiast synchroniedbloków. Ta strona dokumentacji zawiera dobre konstrukcje programistyczne do osiągnięcia bezpieczeństwa wątków.

Zablokuj obiekty obsługują idiomy blokujące, które upraszczają wiele jednoczesnych aplikacji.

Wykonawcy definiują interfejs API wysokiego poziomu do uruchamiania wątków i zarządzania nimi. Implementacje executorów dostarczone przez java.util.concurrent zapewniają zarządzanie pulą wątków odpowiednie dla aplikacji na dużą skalę.

Współbieżne kolekcje ułatwiają zarządzanie dużymi zbiorami danych i mogą znacznie zmniejszyć potrzebę synchronizacji.

Zmienne atomowe mają funkcje minimalizujące synchronizację i pomagające uniknąć błędów spójności pamięci.

ThreadLocalRandom (w JDK 7) zapewnia wydajne generowanie liczb pseudolosowych z wielu wątków.

Lepszym zamiennikiem synchronizowanego jest ReentrantLock , który korzysta z LockAPI

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.

Przykład z zamkami:

class X {
   private final ReentrantLock lock = new ReentrantLock();
   // ...

   public void m() {
     lock.lock();  // block until condition holds
     try {
       // ... method body
     } finally {
       lock.unlock()
     }
   }
 }

Zapoznaj się także z pakietami java.util.concurrent i java.util.concurrent.atomic, aby zapoznać się z innymi konstrukcjami programistycznymi.

Zobacz także podobne pytanie:

Synchronizacja vs Blokada


4

Metoda synchronizacji służy do blokowania wszystkich obiektów Blok synchronizacji służy do blokowania określonych obiektów


3

Zasadniczo są to w większości te same, inne niż jawne informacje na temat monitora obiektu, który jest używany, w porównaniu z niejawnym obiektem. Jedną wadą zsynchronizowanych metod, które moim zdaniem są czasami pomijane, jest to, że używając odniesienia „to” do synchronizacji na sobie, pozostawiasz otwartą możliwość blokowania obiektów zewnętrznych na tym samym obiekcie. To może być bardzo subtelny błąd, jeśli na niego wpadniesz. Synchronizacja na wewnętrznym jawnym obiekcie lub innym istniejącym polu może uniknąć tego problemu, całkowicie obudowując synchronizację.


2

Jak już powiedziano tutaj, zsynchronizowany blok może wykorzystywać zmienną zdefiniowaną przez użytkownika jako obiekt blokady, gdy funkcja synchronizacji używa tylko „tego”. I oczywiście możesz manipulować obszarami swojej funkcji, które powinny być zsynchronizowane. Ale wszyscy mówią, że nie ma różnicy między funkcją zsynchronizowaną a blokiem, który obejmuje całą funkcję, używając „tego” jako obiektu blokady. To nieprawda, różnica polega na kodzie bajtów, który zostanie wygenerowany w obu sytuacjach. W przypadku użycia bloku zsynchronizowanego należy przypisać zmienną lokalną, która zawiera odniesienie do „tego”. W rezultacie będziemy mieli nieco większy rozmiar funkcji (nie dotyczy, jeśli masz tylko kilka funkcji).

Bardziej szczegółowe wyjaśnienie różnicy można znaleźć tutaj: http://www.artima.com/insidejvm/ed2/threadsynchP.html


2

W przypadku metod zsynchronizowanych blokada zostanie uzyskana na obiekcie. Ale jeśli wybierzesz synchronizowany blok, masz opcję określenia obiektu, na którym zostanie uzyskana blokada.

Przykład:

    Class Example {
    String test = "abc";
    // lock will be acquired on String  test object.
    synchronized (test) {
        // do something
    }

   lock will be acquired on Example Object
   public synchronized void testMethod() {
     // do some thing
   } 

   }

2

Wiem, że to stare pytanie, ale po szybkim przeczytaniu tutaj odpowiedzi tak naprawdę nie zauważyłem, aby ktokolwiek wspomniał, że czasami synchronizedmetoda może być niewłaściwą blokadą.
Z Java Concurrency In Practice (str. 72):

public class ListHelper<E> {
  public List<E> list = Collections.syncrhonizedList(new ArrayList<>());
...

public syncrhonized boolean putIfAbsent(E x) {
 boolean absent = !list.contains(x);
if(absent) {
 list.add(x);
}
return absent;
}

Powyższy kod ma wygląd bycia wątku bezpieczny. Jednak w rzeczywistości tak nie jest. W takim przypadku blokada jest uzyskiwana na instancji klasy. Możliwe jest jednak zmodyfikowanie listy przez inny wątek, który nie korzysta z tej metody. Prawidłowe podejście byłoby użyć

public boolean putIfAbsent(E x) {
 synchronized(list) {
  boolean absent = !list.contains(x);
  if(absent) {
    list.add(x);
  }
  return absent;
}
}

Powyższy kod blokowałby wszystkie wątki próbujące zmodyfikować listę przed modyfikowaniem listy, dopóki synchronizowany blok nie zostanie zakończony.


czyta tę książkę w tej chwili ... zastanawiam się ... czy ta lista była prywatna, a nie publiczna i czy tylko zsynchronizowana metoda putIfAbsent byłaby wystarczająca, prawda? problemem jest to, że listę można modyfikować również poza tym ListHelper?
dtc

@ dtc tak, jeśli lista była prywatna i nie wyciekła nigdzie indziej w klasie, to byłoby wystarczające, o ile zaznaczysz każdą inną metodę w klasie, która modyfikuje listę również jako zsynchronizowaną. Jednak zablokowanie całej metody zamiast tylko Listmoże prowadzić do problemów z wydajnością, jeśli istnieje dziennik kodu, który niekoniecznie musi być synchronizowany
aarbor

to ma sens. dziękuję bardzo za odpowiedź! thh, uważam, że książka jest bardzo przydatna w poszerzaniu mojej wiedzy i sposobu podejścia do wielowątkowości, ale wprowadziła także zupełnie nowy świat zamieszania
dtc

2

W praktyce przewaga metod zsynchronizowanych nad zsynchronizowanymi blokami polega na tym, że są one bardziej odporne na idioty; ponieważ nie można wybrać dowolnego obiektu do zablokowania, nie można niewłaściwie używać składni metody synchronicznej do robienia głupich rzeczy, takich jak blokowanie literału łańcucha lub blokowanie zawartości pola zmiennego, które jest zmieniane spod wątków.

Z drugiej strony dzięki metodom zsynchronizowanym nie można zabezpieczyć zamka przed zdobyciem przez dowolny wątek, który może uzyskać odwołanie do obiektu.

Dlatego użycie synchronizacji jako modyfikatora metod lepiej chroni twoich krów-orków przed samookaleczeniem, podczas gdy używanie synchronizowanych bloków w połączeniu z prywatnymi obiektami ostatecznej blokady lepiej chroni twój kod przed krowimi orkami.


1

Ze streszczenia specyfikacji Java: http://www.cs.cornell.edu/andru/javaspec/17.doc.html

Zsynchronizowane polecenie (§14.17) oblicza odniesienie do obiektu; następnie próbuje wykonać akcję blokady na tym obiekcie i nie kontynuuje działania, dopóki akcja blokady nie zakończy się pomyślnie. ...

Metoda zsynchronizowana (§ 8.4.3.5) automatycznie wykonuje akcję blokady po jej wywołaniu; jego ciało nie jest wykonywane, dopóki działanie blokady nie zakończy się pomyślnie. Jeśli metoda jest metodą instancji , blokuje blokadę związaną z instancją, dla której została wywołana (tj. Obiekt, który będzie znany jako ten podczas wykonywania treści metody). Jeśli metoda jest statyczna , blokuje blokadę powiązaną z obiektem klasy reprezentującym klasę, w której metoda jest zdefiniowana. ...

Opierając się na tych opisach, powiedziałbym, że większość poprzednich odpowiedzi jest poprawna, a metoda zsynchronizowana może być szczególnie przydatna w przypadku metod statycznych, w których w innym przypadku musiałbyś dowiedzieć się, jak uzyskać „obiekt klasy reprezentujący klasę, w której metoda była zdefiniowane ”.

Edycja: pierwotnie myślałem, że były to cytaty rzeczywistej specyfikacji Java. Wyjaśniono, że ta strona jest jedynie streszczeniem / wyjaśnieniem specyfikacji


1

TLDR; Nie należy używać synchronizedmodyfikatora ani synchronized(this){...}wyrażenia, ale synchronized(myLock){...}gdzie myLockjest pole ostatniej instancji zawierające prywatny obiekt.


Różnica między użyciem synchronizedmodyfikatora w deklaracji metody a synchronized(..){ }wyrażeniem w treści metody jest następująca:

  • The synchronizedModyfikator podany na podpis metody badaniem
    1. jest widoczny w wygenerowanym JavaDoc,
    2. jest programowo określany przez odbicie podczas testowania modyfikatora metody dla Modifier.SYNCHRONIZED ,
    3. wymaga mniej pisania i wcięcia w porównaniu do synchronized(this) { .... } i
    4. (w zależności od IDE) jest widoczny w konspekcie zajęć i zakończeniu kodu,
    5. używa thisobiektu jako blokady, gdy jest zadeklarowany w metodzie niestatycznej lub klasy obejmującej, gdy jest zadeklarowany w metodzie statycznej.
  • The synchronized(...){...}Ekspresja pozwala
    1. aby zsynchronizować tylko wykonywanie części ciała metody,
    2. do użycia w konstruktorze lub ( statyczny ) bloku inicjującym,
    3. wybrać obiekt blokady, który kontroluje zsynchronizowany dostęp.

Jednak za pomocą synchronizedmodyfikatora lub za synchronized(...) {...}pomocąthis jako obiektu blokady (jak w synchronized(this) {...}) ma tę samą wadę. Oba używają własnej instancji jako obiektu blokady do synchronizacji. Jest to niebezpieczne, ponieważ nie tylko sam obiekt, ale każdy inny zewnętrzny obiekt / kod, który zawiera odniesienie do tego obiektu, może również używać go jako blokady synchronizacji z potencjalnie poważnymi skutkami ubocznymi (obniżenie wydajności i zakleszczenia ).

Dlatego najlepszą praktyką jest, aby nie używać synchronizedmodyfikatora ani synchronized(...)wyrażenia w połączeniu z thisobiektem blokady, ale obiektem blokady prywatnym dla tego obiektu. Na przykład:

public class MyService {
    private final lock = new Object();

    public void doThis() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }

    public void doThat() {
       synchronized(lock) {
          // do code that requires synchronous execution
        }
    }
}

Możesz także użyć wielu obiektów zamków, ale należy zachować szczególną ostrożność, aby nie zagnieździły się, gdy zostaną zagnieżdżone.

public class MyService {
    private final lock1 = new Object();
    private final lock2 = new Object();

    public void doThis() {
       synchronized(lock1) {
          synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThat() and doMore().
          }
    }

    public void doThat() {
       synchronized(lock1) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doMore() may execute concurrently
        }
    }

    public void doMore() {
       synchronized(lock2) {
              // code here is guaranteed not to be executes at the same time
              // as the synchronized code in doThis().
              // doThat() may execute concurrently
        }
    }
}

1

Podejrzewam, że to pytanie dotyczy różnicy między inicjacją Sing Safeon wątku a inicjacją Lazy z blokowaniem Double Check . Zawsze odnoszę się do tego artykułu, gdy muszę wdrożyć jakiś konkretny singleton.

Cóż, to jest Singleton bezpieczny dla wątków :

// Java program to create Thread Safe 
// Singleton class 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

 //synchronized method to control simultaneous access 
  synchronized public static GFG getInstance()  
  { 
    if (instance == null)  
    { 
      // if instance is null, initialize 
      instance = new GFG(); 
    } 
    return instance; 
  } 
} 

Plusy:

  1. Leniwa inicjalizacja jest możliwa.

  2. Jest bezpieczny dla wątków.

Cons:

  1. Metoda getInstance () jest zsynchronizowana, co powoduje niską wydajność, ponieważ wiele wątków nie może uzyskać do niej dostępu jednocześnie.

To jest Leniwa inicjalizacja z blokowaniem Double Check :

// Java code to explain double check locking 
public class GFG  
{ 
  // private instance, so that it can be 
  // accessed by only by getInstance() method 
  private static GFG instance; 

  private GFG()  
  { 
    // private constructor 
  } 

  public static GFG getInstance() 
  { 
    if (instance == null)  
    { 
      //synchronized block to remove overhead 
      synchronized (GFG.class) 
      { 
        if(instance==null) 
        { 
          // if instance is null, initialize 
          instance = new GFG(); 
        } 

      } 
    } 
    return instance; 
  } 
} 

Plusy:

  1. Leniwa inicjalizacja jest możliwa.

  2. Jest także bezpieczny dla wątków.

  3. Wydajność zmniejszona ze względu na zsynchronizowane słowo kluczowe jest pokonana.

Cons:

  1. Za pierwszym razem może to wpłynąć na wydajność.

  2. Wady Metoda blokowania podwójnej kontroli jest znośna, więc może być stosowana do wysokowydajnych aplikacji wielowątkowych.

Więcej informacji można znaleźć w tym artykule:

https://www.geeksforgeeks.org/java-singleton-design-pattern-practices-examples/


-3

Synchronizacja z wątkami. 1) NIGDY nie używaj synchronizacji (tego) w wątku, który nie działa. Synchronizacja z (this) wykorzystuje bieżący wątek jako obiekt wątku blokującego. Ponieważ każdy wątek jest niezależny od innych wątków, nie ma koordynacji synchronizacji. 2) Testy kodu pokazują, że w Javie 1.6 na Macu synchronizacja metod nie działa. 3) zsynchronizowane (lockObj), gdzie lockObj jest wspólnym wspólnym obiektem wszystkich wątków synchronizujących na nim będzie działać. 4) Działa ReenterantLock.lock () i .unlock (). Zobacz samouczki Java na ten temat.

Poniższy kod pokazuje te punkty. Zawiera również bezpieczny wątek Vector, który zostałby zastąpiony ArrayList, aby pokazać, że wiele wątków dodających do Vector nie traci żadnych informacji, podczas gdy to samo z ArrayList może utracić informacje. 0) Aktualny kod pokazuje utratę informacji z powodu warunków wyścigu A) Skomentuj bieżącą linię A i odkomentuj linię A nad nią, a następnie uruchom, metoda traci dane, ale nie powinna. B) Odwróć krok A, odkomentuj B i // blok końcowy}. Następnie uruchom, aby zobaczyć wyniki bez utraty danych C) Skomentuj B, odkomentuj C. Uruchom, patrz synchronizacja (to) traci dane, zgodnie z oczekiwaniami. Nie mam czasu na ukończenie wszystkich odmian, mam nadzieję, że to pomoże. Jeśli synchronizacja jest włączona (ta) lub metoda synchronizacji działa, proszę podać, którą wersję Java i systemu operacyjnego przetestowałeś. Dziękuję Ci.

import java.util.*;

/** RaceCondition - Shows that when multiple threads compete for resources 
     thread one may grab the resource expecting to update a particular 
     area but is removed from the CPU before finishing.  Thread one still 
     points to that resource.  Then thread two grabs that resource and 
     completes the update.  Then thread one gets to complete the update, 
     which over writes thread two's work.
     DEMO:  1) Run as is - see missing counts from race condition, Run severa times, values change  
            2) Uncomment "synchronized(countLock){ }" - see counts work
            Synchronized creates a lock on that block of code, no other threads can 
            execute code within a block that another thread has a lock.
        3) Comment ArrayList, unComment Vector - See no loss in collection
            Vectors work like ArrayList, but Vectors are "Thread Safe"
         May use this code as long as attribution to the author remains intact.
     /mf
*/ 

public class RaceCondition {
    private ArrayList<Integer> raceList = new ArrayList<Integer>(); // simple add(#)
//  private Vector<Integer> raceList = new Vector<Integer>(); // simple add(#)

    private String countLock="lock";    // Object use for locking the raceCount
    private int raceCount = 0;        // simple add 1 to this counter
    private int MAX = 10000;        // Do this 10,000 times
    private int NUM_THREADS = 100;    // Create 100 threads

    public static void main(String [] args) {
    new RaceCondition();
    }

    public RaceCondition() {
    ArrayList<Thread> arT = new ArrayList<Thread>();

    // Create thread objects, add them to an array list
    for( int i=0; i<NUM_THREADS; i++){
        Thread rt = new RaceThread( ); // i );
        arT.add( rt );
    }

    // Start all object at once.
    for( Thread rt : arT ){
        rt.start();
    }

    // Wait for all threads to finish before we can print totals created by threads
    for( int i=0; i<NUM_THREADS; i++){
        try { arT.get(i).join(); }
        catch( InterruptedException ie ) { System.out.println("Interrupted thread "+i); }
    }

    // All threads finished, print the summary information.
    // (Try to print this informaiton without the join loop above)
    System.out.printf("\nRace condition, should have %,d. Really have %,d in array, and count of %,d.\n",
                MAX*NUM_THREADS, raceList.size(), raceCount );
    System.out.printf("Array lost %,d. Count lost %,d\n",
             MAX*NUM_THREADS-raceList.size(), MAX*NUM_THREADS-raceCount );
    }   // end RaceCondition constructor



    class RaceThread extends Thread {
    public void run() {
        for ( int i=0; i<MAX; i++){
        try {
            update( i );        
        }    // These  catches show when one thread steps on another's values
        catch( ArrayIndexOutOfBoundsException ai ){ System.out.print("A"); }
        catch( OutOfMemoryError oome ) { System.out.print("O"); }
        }
    }

    // so we don't lose counts, need to synchronize on some object, not primitive
    // Created "countLock" to show how this can work.
    // Comment out the synchronized and ending {, see that we lose counts.

//    public synchronized void update(int i){   // use A
    public void update(int i){                  // remove this when adding A
//      synchronized(countLock){            // or B
//      synchronized(this){             // or C
        raceCount = raceCount + 1;
        raceList.add( i );      // use Vector  
//          }           // end block for B or C
    }   // end update

    }   // end RaceThread inner class


} // end RaceCondition outter class

1
Synchronizacja z „(ta)” wykonuje pracę, a nie nie „używać bieżącego wątku jako obiekt synchronizacji”, chyba że obecny obiekt jest klasy, która rozciąga się wątek. -1
Markiz Lorne
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.