Zsynchronizowana metoda Java blokady obiektu lub metody?

191

Jeśli mam 2 zsynchronizowane metody w tej samej klasie, ale każda ma dostęp do różnych zmiennych, czy 2 wątki mogą uzyskać dostęp do tych 2 metod jednocześnie? Czy blokada występuje na obiekcie, czy staje się tak specyficzna jak zmienne w zsynchronizowanej metodzie?

Przykład:

class X {

    private int a;
    private int b;

    public synchronized void addA(){
        a++;
    }

    public synchronized void addB(){
        b++;
    }

}

Czy 2 wątki mogą uzyskać dostęp do tego samego wystąpienia klasy X x.addA() i x.addB()jednocześnie?

wuntee
źródło

Odpowiedzi:

199

Jeśli zadeklarujesz metodę jako zsynchronizowaną (tak jak piszesz public synchronized void addA()), synchronizujesz cały obiekt, więc dwa wątki uzyskujące dostęp do innej zmiennej z tego samego obiektu i tak by się blokowały.

Jeśli chcesz synchronizować tylko jedną zmienną na raz, aby dwa wątki nie blokowały się nawzajem podczas uzyskiwania dostępu do różnych zmiennych, synchronizuj je oddzielnie w synchronized ()blokach. Gdyby ai bbyły odwołania do obiektów, użyłbyś:

public void addA() {
    synchronized( a ) {
        a++;
    }
}

public void addB() {
    synchronized( b ) {
        b++;
    }
}

Ale ponieważ są prymitywami, nie możesz tego zrobić.

Proponuję zamiast tego użyć AtomicInteger :

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

    public void addA(){
        a.incrementAndGet();
    }

    public void addB(){ 
        b.incrementAndGet();
    }
}
OscarRyz
źródło
181
Jeśli synchronizujesz metodę, blokujesz cały obiekt, więc dwa wątki uzyskujące dostęp do innej zmiennej z tego samego obiektu i tak by się blokowały. To trochę wprowadza w błąd. Synchronizacja w metodzie jest funkcjonalnie równoważna z synchronized (this)blokiem wokół ciała metody. Obiekt „this” nie zostaje zablokowany, raczej obiekt „this” jest używany jako muteks, a ciało nie może wykonywać jednocześnie z innymi sekcjami kodu również zsynchronizowanymi z „this”. Nie ma wpływu na inne pola / metody „tego”, które nie są zsynchronizowane.
Mark Peters
13
Tak, to jest naprawdę mylące. Na przykład - Spójrz na to - stackoverflow.com/questions/14447095/… - Podsumowanie: Blokowanie odbywa się tylko na poziomie metody zsynchronizowanej, a do zmiennych instancji obiektu można uzyskać dostęp za pomocą innego wątku
mac.
5
Pierwszy przykład jest zasadniczo zepsuty. Gdyby ai bbyły obiektami, np. IntegerS, synchronizowałeś instancje, które zastępujesz innymi obiektami podczas stosowania ++operatora.
Holger,
napraw swoją odpowiedź i zainicjuj AtomicInteger: AtomicInteger a = new AtomicInteger (0);
Mehdi
Może ta odpowiedź powinna zostać zaktualizowana o wyjaśnienie w tym drugim na temat synchronizacji samego obiektu: stackoverflow.com/a/10324280/1099452
lucasvc
71

Zsynchronizowany w deklaracji metody jest cukier składniowy w tym celu:

 public void addA() {
     synchronized (this) {
          a++;
     }
  }

W metodzie statycznej jest to cukier syntaktyczny:

 ClassA {
     public static void addA() {
          synchronized(ClassA.class) {
              a++;
          }
 }

Myślę, że gdyby projektanci Javy wiedzieli, co rozumie się teraz na temat synchronizacji, nie dodaliby cukru syntaktycznego, ponieważ najczęściej prowadzi to do złych implementacji współbieżności.

Yishai
źródło
3
Nie prawda. metoda zsynchronizowana generuje inny kod bajtowy niż zsynchronizowany (obiekt). Choć funkcjonalność jest równoważna, to coś więcej niż cukier syntaktyczny.
Steve Kuo,
10
Nie sądzę, że „cukier syntaktyczny” jest ściśle zdefiniowany jako odpowiednik kodu bajtowego. Chodzi o to, że jest funkcjonalnie równoważny.
Yishai
1
Gdyby projektanci Javy wiedzieli, co już wiadomo o monitorach, zrobiliby to inaczej, zamiast w zasadzie emulować wewnętrzne cechy Uniksa. Per Brinch Hansen powiedział „najwyraźniej na próżno pracowałem”, gdy zobaczył prymitywne współbieżności Javy .
Markiz Lorne
To prawda. Przykład podany przez OP wydaje się blokować każdą metodę, ale w rzeczywistości wszystkie blokują ten sam obiekt. Bardzo zwodnicza składnia. Po korzystaniu z Javy od ponad 10 lat nie wiedziałem o tym. Dlatego unikałbym zsynchronizowanych metod z tego powodu. Zawsze myślałem, że dla każdej metody, która została zdefiniowana za pomocą synchronizacji, tworzony jest niewidoczny obiekt.
Peter Quiring
21

Z „Samouczków Java ™” na temat metod synchronizowanych :

Po pierwsze, niemożliwe jest przeplatanie dwóch wywołań metod zsynchronizowanych dla tego samego obiektu . 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 wykonany z obiektem.

Z „Samouczków Java ™” na temat synchronizowanych bloków :

Instrukcje zsynchronizowane są również przydatne do poprawy współbieżności z drobnoziarnistą synchronizacją. Załóżmy na przykład, że klasa MsLunch ma dwa pola instancji, c1 i c2, które nigdy nie są używane razem. Wszystkie aktualizacje tych pól muszą być zsynchronizowane, ale nie ma powodu, aby zapobiegać przeplataniu aktualizacji c1 z aktualizacją c2 - a dzięki temu zmniejsza się współbieżność poprzez tworzenie niepotrzebnego blokowania. Zamiast używać metod zsynchronizowanych lub w inny sposób korzystać z powiązanej z tym blokady, tworzymy dwa obiekty wyłącznie w celu zapewnienia blokad.

(Moje podkreślenie)

Załóżmy, że masz 2 zmienne nieprzeplatające . Więc chcesz mieć dostęp do każdego z różnych wątków jednocześnie. Musisz zdefiniować blokadę nie na samej klasie obiektu, ale na klasie Object jak poniżej (przykład z drugiego łącza Oracle):

public class MsLunch {

    private long c1 = 0;
    private long c2 = 0;

    private Object lock1 = new Object();
    private Object lock2 = new Object();

    public void inc1() {
        synchronized(lock1) {
            c1++;
        }
    }

    public void inc2() {
        synchronized(lock2) {
            c2++;
        }
    }
}
MehdiMAH
źródło
14

Dostęp do blokady znajduje się na obiekcie, a nie na metodzie. Które zmienne są dostępne w ramach metody, nie ma znaczenia.

Dodanie „zsynchronizowanej” do metody oznacza, że ​​wątek uruchamiający kod musi uzyskać blokadę obiektu przed kontynuowaniem. Dodanie „zsynchronizowany statycznie” oznacza, że ​​wątek uruchamiający kod musi uzyskać blokadę obiektu klasy przed kontynuowaniem. Alternatywnie możesz owinąć kod w taki blok:

public void addA() {
    synchronized(this) {
        a++;
    }
}

dzięki czemu można określić obiekt, którego blokada musi zostać uzyskana.

Jeśli chcesz uniknąć blokowania obiektu zawierającego, możesz wybrać między:

Nathan Hughes
źródło
7

Z linku do dokumentacji Oracle

Synchronizacja metod ma dwa efekty:

Po pierwsze, niemożliwe jest przeplatanie dwóch wywołań metod zsynchronizowanych dla tego samego obiektu. 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 wykonany 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

Zajrzyj na tę stronę dokumentacji, aby zrozumieć wewnętrzne blokady i ich zachowanie.

To odpowie na twoje pytanie: na tym samym obiekcie x nie można wywoływać x.addA () i x.addB () w tym samym czasie, gdy trwa wykonywanie jednej z metod synchronizowanych.

Aditya W
źródło
4

Jeśli masz metody, które nie są zsynchronizowane i uzyskują dostęp do zmiennych instancji i zmieniają je. W twoim przykładzie:

 private int a;
 private int b;

dowolna liczba wątków może uzyskać dostęp do tych niezsynchronizowanych metod w tym samym czasie, gdy inny wątek jest w synchronizowanej metodzie tego samego obiektu i może dokonywać zmian w zmiennych instancji. Na przykład:

 public void changeState() {
      a++;
      b++;
    }

Należy unikać scenariusza, w którym niezsynchronizowane metody uzyskują dostęp do zmiennych instancji i zmieniają je, w przeciwnym razie nie ma sensu używać metod synchronizowanych.

W poniższym scenariuszu:

class X {

        private int a;
        private int b;

        public synchronized void addA(){
            a++;
        }

        public synchronized void addB(){
            b++;
        }
     public void changeState() {
          a++;
          b++;
        }
    }

Tylko jeden wątek może być w metodzie addA lub addB, ale jednocześnie dowolna liczba wątków może wejść do metody changeState. Żadne dwa wątki nie mogą jednocześnie wprowadzić addA i addB (z powodu blokowania na poziomie obiektu), ale jednocześnie dowolna liczba wątków może wejść do changeState.

Goyal Vicky
źródło
3

Możesz zrobić coś takiego: W tym przypadku używasz blokady na aib, aby zsynchronizować zamiast blokady na „to”. Nie możemy użyć int, ponieważ pierwotne wartości nie mają blokad, dlatego używamy liczby całkowitej.

class x{
   private Integer a;
   private Integer b;
   public void addA(){
      synchronized(a) {
         a++;
      }
   }
   public synchronized void addB(){
      synchronized(b) {
         b++;
      }
   }
}
dsmith
źródło
3

Tak, zablokuje inną metodę, ponieważ metoda zsynchronizowana ma zastosowanie do CAŁEGO obiektu klasy jak wskazano .... ale tak czy inaczej zablokuje wykonanie innego wątku TYLKO podczas wykonywania sumy w dowolnej metodzie, do której wejdzie metoda addA lub addB, ponieważ kiedy zakończy się ... jeden wątek uwolni obiekt, a drugi wątek uzyska dostęp do drugiej metody i tak dalej działa idealnie.

Mam na myśli, że „zsynchronizowany” jest stworzony właśnie w celu zablokowania drugiemu wątkowi dostępu do innego podczas wykonywania określonego kodu. WRESZCIE TEN KOD DZIAŁA WYGODNIE.

Na koniec, jeśli istnieją zmienne „a” i „b”, a nie tylko unikalna zmienna „a” lub jakakolwiek inna nazwa, nie ma potrzeby synchronizowania tych metod, ponieważ dostęp do innej zmiennej jest całkowicie bezpieczny (inna pamięć Lokalizacja).

class X {

private int a;
private int b;

public void addA(){
    a++;
}

public void addB(){
    b++;
}}

Będzie również działać

Jose Velandia
źródło
2

Ten przykład (choć nie ładny) może zapewnić lepszy wgląd w mechanizm blokujący. Jeśli incrementA jest zsynchronizowany , a incrementB nie jest zsynchronizowany , to incrementB zostanie wykonany jak najszybciej, ale jeśli incrementB jest również zsynchronizowany , musi „poczekać” na zakończenie incrementA , zanim incrementB może wykonywać swoje zadania.

Obie metody są wywoływane w jednym wystąpieniu - obiekt, w tym przykładzie jest to: zadanie , a wątki „konkurujące” są wątkiem i głównym .

Spróbuj z „ zsynchronizowanym ” w inkrementie B i bez niego, a zobaczysz inne wyniki. Jeśli inkrement B jest również „ zsynchronizowany ”, musi poczekać na zakończenie inkrementu A (). Uruchom kilka razy w każdym wariancie.

class LockTest implements Runnable {
    int a = 0;
    int b = 0;

    public synchronized void incrementA() {
        for (int i = 0; i < 100; i++) {
            this.a++;
            System.out.println("Thread: " + Thread.currentThread().getName() + "; a: " + this.a);
        }
    }

    // Try with 'synchronized' and without it and you will see different results
    // if incrementB is 'synchronized' as well then it has to wait for incrementA() to finish

    // public void incrementB() {
    public synchronized void incrementB() {
        this.b++;
        System.out.println("*************** incrementB ********************");
        System.out.println("Thread: " + Thread.currentThread().getName() + "; b: " + this.b);
        System.out.println("*************** incrementB ********************");
    }

    @Override
    public void run() {
        incrementA();
        System.out.println("************ incrementA completed *************");
    }
}

class LockTestMain {
    public static void main(String[] args) throws InterruptedException {
        LockTest job = new LockTest();
        Thread aThread = new Thread(job);
        aThread.setName("aThread");
        aThread.start();
        Thread.sleep(1);
        System.out.println("*************** 'main' calling metod: incrementB **********************");
        job.incrementB();
    }
}
Nenad Bulatovic
źródło
1

W synchronizacji Java, jeśli wątek chce wejść do metody synchronizacji, uzyska blokadę na wszystkich zsynchronizowanych metodach tego obiektu, a nie tylko na jednej zsynchronizowanej metodzie, której używa wątek. Tak więc wątek wykonujący addA () uzyska blokadę na addA () i addB (), ponieważ oba są zsynchronizowane, więc inne wątki z tym samym obiektem nie mogą wykonać addB ().

Sreedhar Reddy
źródło
0

Może to nie działać, ponieważ boksowanie i autoboksowanie z Integer na int i viceversa zależą od JVM i istnieje duże prawdopodobieństwo, że dwie różne liczby mogą zostać zaszyfrowane pod tym samym adresem, jeśli są między -128 a 127.

Sriharsha grv
źródło