Java synchronisierte Methodensperre für Objekt oder Methode?


191

Wenn ich zwei synchronisierte Methoden in derselben Klasse habe, aber jeweils auf unterschiedliche Variablen zugreift, können 2 Threads gleichzeitig auf diese beiden Methoden zugreifen? Tritt die Sperre für das Objekt auf oder wird sie so spezifisch wie die Variablen innerhalb der synchronisierten Methode?

Beispiel:

class X {

    private int a;
    private int b;

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

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

}

Kann 2 Threads greifen auf dieselbe Instanz der Klasse X Durchführung x.addA() und x.addB()zur gleichen Zeit?

Antworten:


197

Wenn Sie die Methode als synchronisiert deklarieren (wie durch Tippen public synchronized void addA()), synchronisieren Sie das gesamte Objekt, sodass sich zwei Threads, die von demselben Objekt aus auf eine andere Variable zugreifen, ohnehin gegenseitig blockieren.

Wenn Sie jeweils nur für eine Variable synchronisieren möchten, damit sich zwei Threads beim Zugriff auf verschiedene Variablen nicht gegenseitig blockieren, müssen Sie sie separat in synchronized ()Blöcken synchronisieren . Wenn aund bObjektreferenzen wären, würden Sie verwenden:

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

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

Aber da es sich um Primitive handelt, können Sie dies nicht tun.

Ich würde Ihnen empfehlen, stattdessen AtomicInteger zu verwenden:

import java.util.concurrent.atomic.AtomicInteger;

class X {

    AtomicInteger a;
    AtomicInteger b;

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

    public void addB(){ 
        b.incrementAndGet();
    }
}

181
Wenn Sie die Methode synchronisieren, sperren Sie das gesamte Objekt, sodass sich zwei Threads, die von demselben Objekt aus auf eine andere Variable zugreifen, ohnehin gegenseitig blockieren. Das ist etwas irreführend. Das Synchronisieren auf der Methode entspricht funktional einem synchronized (this)Block um den Hauptteil der Methode. Das Objekt "this" wird nicht gesperrt, sondern das Objekt "this" wird als Mutex verwendet, und es wird verhindert, dass der Body gleichzeitig mit anderen Codeabschnitten ausgeführt wird, die ebenfalls mit "this" synchronisiert sind. Es hat keine Auswirkung auf andere Felder / Methoden von "this", die nicht synchronisiert sind.
Mark Peters

13
Ja, es ist wirklich irreführend. Ein echtes Beispiel - Sehen Sie sich das an - stackoverflow.com/questions/14447095/… - Zusammenfassung: Das Sperren erfolgt nur auf synchronisierter Methodenebene, und auf die Instanzvariablen des Objekts kann von einem anderen Thread zugegriffen werden
mac

5
Das erste Beispiel ist grundlegend gebrochen. Wenn aund bwaren Objekte, z. B. Integers, haben Sie beim Anwenden des Operators auf Instanzen synchronisiert, die Sie durch andere Objekte ersetzen++ .
Holger

Korrigieren Sie Ihre Antwort und initialisieren Sie die AtomicInteger: AtomicInteger a = new AtomicInteger (0);
Mehdi

Vielleicht sollte diese Antwort mit der in dieser anderen über die Synchronisierung auf dem Objekt selbst erläuterten aktualisiert werden: stackoverflow.com/a/10324280/1099452
lucasvc

71

In der Methodendeklaration ist syntaktischer Zucker dafür synchronisiert:

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

Bei einer statischen Methode ist es syntaktischer Zucker dafür:

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

Ich denke, wenn die Java-Designer damals gewusst hätten, was jetzt über Synchronisation verstanden wird, hätten sie den syntaktischen Zucker nicht hinzugefügt, da dies häufig zu schlechten Implementierungen der Parallelität führt.


3
Nicht wahr. Die synchronisierte Methode generiert einen anderen Bytecode als die synchronisierte (Objekt). Während Funktionalität gleichwertig ist, ist es mehr als nur syntaktischer Zucker.
Steve Kuo

10
Ich denke nicht, dass "syntaktischer Zucker" streng als Bytecode-Äquivalent definiert ist. Der Punkt ist, dass es funktional äquivalent ist.
Yishai

1
Wenn die Java-Designer gewusst hätten, was bereits über Monitore bekannt ist, hätten / hätten sie es anders machen sollen, anstatt im Grunde die Innereien von Unix zu emulieren. Per Brinch Hansen sagte, "klar, ich habe vergeblich gearbeitet", als er die Java-Parallelitätsprimitive sah .
Marquis von Lorne

Das ist wahr. Das von OP gegebene Beispiel scheint jede Methode zu sperren, aber tatsächlich sperren sie alle dasselbe Objekt. Sehr trügerische Syntax. Nachdem ich Java über 10 Jahre lang benutzt hatte, wusste ich das nicht. Aus diesem Grund würde ich synchronisierte Methoden vermeiden. Ich dachte immer, dass für jede Methode, die mit synchronisiert definiert wurde, ein unsichtbares Objekt erstellt wurde.
Peter Quiring

21

Aus "The Java ™ Tutorials" zu synchronisierten Methoden :

Erstens ist es nicht möglich, dass zwei Aufrufe synchronisierter Methoden für dasselbe Objekt verschachtelt werden. Wenn ein Thread eine synchronisierte Methode für ein Objekt ausführt, blockieren alle anderen Threads, die synchronisierte Methoden für denselben Objektblock aufrufen (Ausführung aussetzen), bis der erste Thread mit dem Objekt fertig ist.

Aus "The Java ™ Tutorials" zu synchronisierten Blöcken :

Synchronisierte Anweisungen sind auch nützlich, um die Parallelität mit einer fein abgestimmten Synchronisation zu verbessern. Angenommen, die Klasse MsLunch verfügt beispielsweise über zwei Instanzfelder, c1 und c2, die niemals zusammen verwendet werden. Alle Aktualisierungen dieser Felder müssen synchronisiert werden. Es gibt jedoch keinen Grund, zu verhindern, dass eine Aktualisierung von c1 mit einer Aktualisierung von c2 verschachtelt wird. Dadurch wird die Parallelität verringert, indem unnötige Blockierungen verursacht werden. Anstatt synchronisierte Methoden zu verwenden oder die damit verbundene Sperre anderweitig zu verwenden, erstellen wir zwei Objekte ausschließlich, um Sperren bereitzustellen.

(Hervorhebung von mir)

Angenommen, Sie haben 2 nicht verschachtelte Variablen. Sie möchten also gleichzeitig von einem anderen Thread aus auf jeden zugreifen. Sie müssen die Sperre nicht für die Objektklasse selbst definieren, sondern für die Klasse Object wie unten (Beispiel aus dem zweiten Oracle-Link):

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++;
        }
    }
}

14

Die Sperre, auf die zugegriffen wird, bezieht sich auf das Objekt und nicht auf die Methode. Auf welche Variablen innerhalb der Methode zugegriffen wird, spielt keine Rolle.

Das Hinzufügen von "synchronisiert" zur Methode bedeutet, dass der Thread, in dem der Code ausgeführt wird, die Sperre für das Objekt erhalten muss, bevor Sie fortfahren können. Das Hinzufügen von "statisch synchronisiert" bedeutet, dass der Thread, in dem der Code ausgeführt wird, die Sperre für das Klassenobjekt erhalten muss, bevor Sie fortfahren können. Alternativ können Sie Code in einen Block wie folgt einschließen:

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

Damit können Sie das Objekt angeben, dessen Sperre erworben werden muss.

Wenn Sie das Sperren des enthaltenen Objekts vermeiden möchten, können Sie zwischen folgenden Optionen wählen:


7

Von der Oracle - Dokumentation Link

Das Synchronisieren von Methoden hat zwei Auswirkungen:

Erstens ist es nicht möglich, dass zwei Aufrufe synchronisierter Methoden für dasselbe Objekt verschachtelt werden. Wenn ein Thread eine synchronisierte Methode für ein Objekt ausführt, blockieren alle anderen Threads, die synchronisierte Methoden für denselben Objektblock aufrufen (Ausführung aussetzen), bis der erste Thread mit dem Objekt fertig ist.

Zweitens wird beim Beenden einer synchronisierten Methode automatisch eine Vorher-Beziehung zu einem nachfolgenden Aufruf einer synchronisierten Methode für dasselbe Objekt hergestellt. Dies garantiert, dass Änderungen am Status des Objekts für alle Threads sichtbar sind

Werfen Sie einen Blick auf dieser Dokumentation Seite intrinsische Sperren und Sperrverhalten zu verstehen.

Dies beantwortet Ihre Frage: Auf demselben Objekt x können Sie x.addA () und x.addB () nicht gleichzeitig aufrufen, wenn eine der synchronisierten Methoden ausgeführt wird.


4

Wenn Sie einige Methoden haben, die nicht synchronisiert sind und auf die Instanzvariablen zugreifen und diese ändern. In Ihrem Beispiel:

 private int a;
 private int b;

Eine beliebige Anzahl von Threads kann gleichzeitig auf diese nicht synchronisierten Methoden zugreifen, wenn sich ein anderer Thread in der synchronisierten Methode desselben Objekts befindet, und Änderungen an Instanzvariablen vornehmen. Zum Beispiel: -

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

Sie müssen das Szenario vermeiden, dass nicht synchronisierte Methoden auf die Instanzvariablen zugreifen und diese ändern, da es sonst keinen Sinn macht, synchronisierte Methoden zu verwenden.

Im folgenden Szenario: -

class X {

        private int a;
        private int b;

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

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

Nur einer der Threads kann sich entweder in der addA- oder der addB-Methode befinden, gleichzeitig kann jedoch eine beliebige Anzahl von Threads in die changeState-Methode eingegeben werden. Keine zwei Threads können gleichzeitig addA und addB eingeben (aufgrund der Sperre auf Objektebene), aber gleichzeitig können beliebig viele Threads changeState eingeben.


3

Sie können Folgendes tun. In diesem Fall verwenden Sie die Sperre für a und b zum Synchronisieren anstelle der Sperre für "this". Wir können int nicht verwenden, da primitive Werte keine Sperren haben, daher verwenden wir Integer.

class x{
   private Integer a;
   private Integer b;
   public void addA(){
      synchronized(a) {
         a++;
      }
   }
   public synchronized void addB(){
      synchronized(b) {
         b++;
      }
   }
}

3

Ja, die andere Methode wird blockiert, da die synchronisierte Methode wie angegeben auf das WHOLE- Klassenobjekt angewendet wird. Trotzdem wird die Ausführung des anderen Threads NUR blockiert, während die Summe in der eingegebenen Methode addA oder addB ausgeführt wird, denn wenn sie beendet ist ... der eine Thread befreit das Objekt und der andere Thread greift auf die andere Methode zu und so weiter.

Ich meine, das "synchronisierte" ist genau dafür gemacht, den anderen Thread daran zu hindern, während einer bestimmten Codeausführung auf einen anderen zuzugreifen. SO ENDLICH FUNKTIONIERT DIESER CODE FEIN.

Wenn es eine 'a'- und eine' b'-Variable gibt, nicht nur eine eindeutige Variable 'a' oder einen anderen Namen, müssen diese Methoden nicht synchronisiert werden, da der Zugriff auf andere Variablen (anderer Speicher) absolut sicher ist Ort).

class X {

private int a;
private int b;

public void addA(){
    a++;
}

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

Wird auch funktionieren


2

Dieses Beispiel (obwohl nicht hübsch) bietet mehr Einblick in den Sperrmechanismus. Wenn Inkremente werden synchronisiert und incrementB ist nicht synchronisiert , dann incrementB wird so schnell wie möglich ausgeführt werden, aber wenn incrementB ebenfalls synchronisiert dann muss es ‚wait‘ für Inkremente bis zum Ende, bevor incrementB seine Arbeit erledigen kann.

Beide Methoden werden für ein einzelnes Instanzobjekt aufgerufen. In diesem Beispiel ist es: job , und 'konkurrierende' Threads sind aThread und main . .

Versuchen Sie es mit ' synchronisiert ' in inkrementB und ohne es und Sie werden unterschiedliche Ergebnisse sehen. Wenn inkrementB ebenfalls ' synchronisiert ' ist, muss es warten, bis inkrementA () beendet ist. Führen Sie jede Variante mehrmals aus.

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();
    }
}

1

Wenn ein Thread bei der Java-Synchronisierung eine Synchronisierungsmethode eingeben möchte, wird er für alle synchronisierten Methoden dieses Objekts gesperrt, nicht nur für eine synchronisierte Methode, die der Thread verwendet. Ein Thread, der addA () ausführt, erhält also eine Sperre für addA () und addB (), da beide synchronisiert sind. Andere Threads mit demselben Objekt können addB () also nicht ausführen.


0

Dies funktioniert möglicherweise nicht, da das Boxen und Autoboxen von Integer nach int und umgekehrt von JVM abhängt und die Wahrscheinlichkeit groß ist, dass zwei verschiedene Nummern an dieselbe Adresse gehasht werden, wenn sie zwischen -128 und 127 liegen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.