Warum ist synchronisierter Block besser als synchronisierte Methode?


75

Ich habe angefangen, Synchronisation im Threading zu lernen.

Synchronisierte Methode:

public class Counter {

   private static int count = 0;

   public static synchronized int getCount() {
      return count;
   }

   public synchronized setCount(int count) {
      this.count = count;
   }

}

Synchronisierter Block:

public class Singleton {

   private static volatile Singleton _instance;

   public static Singleton getInstance() {
      if (_instance == null) {
         synchronized(Singleton.class) {
            if (_instance == null)
               _instance = new Singleton();
         }
      }
      return _instance;
   }
}

Wann sollte ich synchronizedMethode und synchronizedBlock verwenden?

Warum ist synchronizedBlock besser als synchronizedMethode?



1
Im ersten Fall würde ich eine AtomicInteger verwenden und im zweiten Fall würde ich eine enumfür einen Singleton verwenden.
Peter Lawrey

Wo steht, dass es besser ist? Was ist die Basis für Ihre Frage?
Marquis von Lorne

Antworten:


88

Es geht nicht besser, nur anders.

Wenn Sie eine Methode synchronisieren, synchronisieren Sie effektiv mit dem Objekt selbst. Bei einer statischen Methode synchronisieren Sie mit der Klasse des Objekts. Die folgenden zwei Codeteile werden also auf dieselbe Weise ausgeführt:

public synchronized int getCount() {
    // ...
}

Das ist genau so, wie du das geschrieben hast.

public int getCount() {
    synchronized (this) {
        // ...
    }
}

Wenn Sie die Synchronisation mit einem bestimmten Objekt steuern möchten oder nur ein Teil einer Methode mit dem Objekt synchronisiert werden soll, geben Sie einen synchronizedBlock an. Wenn Sie das synchronizedSchlüsselwort in der Methodendeklaration verwenden, wird die gesamte Methode mit dem Objekt oder der Klasse synchronisiert.


Ja, dasselbe gilt für Klassenvariablen und -methoden, außer dass der Monitor für das entsprechende Klassenobjekt und nicht der der Instanz (this) erhalten wird.
Aniket Thakur

7
Ja, es ist anders, aber es gibt sehr gute Gründe, warum Sie nie synchronisieren möchten, thissodass diese Antwort den Punkt ein wenig verfehlt.
Voo

4
@Voo: Ich bin nicht der Meinung, dass Sie niemals synchronisieren möchten this. In der Tat ist es ein Vorteil, wenn Sie eine Reihe synchronisierter Methoden nacheinander aufrufen möchten. Sie können das Objekt für die Dauer synchronisieren und müssen sich nicht um das Freigeben und erneute Abrufen von Sperren für jeden Methodenaufruf kümmern. Es gibt viele verschiedene Muster, die für die Synchronisation verwendet werden können, und jedes hat je nach Situation seine eigenen Vor- und Nachteile.
Erick Robertson

2
Es gibt absolut keinen Unterschied zwischen einer synchronisierten Methode und einer Methode, deren einziger Block der obersten Ebene ist synchronized(someObj). Tatsächlich generiert der Compiler Code für die Synchronisierung, als ob someObj==this. Dies hat also keinen Vorteil, aber Sie setzen interne Details der Außenwelt aus, wodurch die Kapselung eindeutig unterbrochen wird. Nun, es gibt einen Vorteil: Sie sparen ungefähr 20 Byte.
Voo

3
@Voo Das Threading-Verhalten einer Klasse ist kein internes Detail. Es ist Teil Ihres Codevertrags. Es wird häufig weggelassen, aber ob ich eine Klasse in einer gleichzeitigen Umgebung verwenden kann oder nicht, kann ein großer Teil ihrer Verwendung sein. Das synchronisierte Schlüsselwort gibt an, wie Sie das Threading verwalten. Ich halte das jedoch nicht für schlecht, da Sie Ihre Threading-Kontrolle ändern und das Schlüsselwort entfernen können, ohne einen Client-Code zu beschädigen.
Spina

45

Obwohl dies aus Sicherheitsgründen normalerweise kein Problem darstellt, ist es besser, die Synchronisierung für ein privates Objekt zu verwenden, als es für eine Methode zu verwenden.

Wenn Sie es auf die Methode setzen, verwenden Sie die Sperre des Objekts selbst, um die Thread-Sicherheit zu gewährleisten. Mit dieser Art von Mechanismus kann ein böswilliger Benutzer Ihres Codes auch die Sperre für Ihr Objekt erhalten und für immer behalten, wodurch andere Threads effektiv blockiert werden. Ein nicht böswilliger Benutzer kann das gleiche versehentlich tun.

Wenn Sie die Sperre eines privaten Datenelements verwenden, können Sie dies verhindern, da ein böswilliger Benutzer die Sperre für Ihr privates Objekt nicht erhalten kann.

private final Object lockObject = new Object();

public void getCount() {
    synchronized( lockObject ) {
        ...
    }
}

Diese Technik wird in Blochs Effective Java (2. Aufl.), Punkt 70 erwähnt


2
Muss nicht einmal böswillig sein, es ist sehr einfach und unschuldig, ein Objekt zu verwenden, das Sie von irgendwoher zum Sperren erhalten (z. B. weil Sie den Zugriff auf genau dieses Objekt synchronisieren möchten). Am besten vermeiden Sie solche Probleme
Voo

7
@wolfcastle: Können Sie erklären, was ein "böswilliger Benutzer Ihres Codes" ist? Wie sind sie bösartig, indem sie ihre eigene Anwendung durcheinander bringen?
Erick Robertson

11
@ErickRobertson Image Sie bieten eine Art Service mit einer öffentlichen API an. Wenn Ihre API ein veränderliches Objekt verfügbar macht, dessen Vorgang vom Sperren dieses Objekts abhängt, kann ein böswilliger Client das Objekt abrufen, sperren und für immer eine Schleife ausführen, wobei die Sperre beibehalten wird. Dies könnte verhindern, dass andere Clients ordnungsgemäß arbeiten können. Es handelt sich im Grunde genommen um einen Denial-of-Service-Angriff. Sie vermasseln also nicht nur ihre eigene Anwendung.
Wolfcastle

Obwohl es eine alte Antwort ist, habe ich einen Kommentar zu dieser Antwort. "Es ist für einen böswilligen Benutzer unmöglich, die Sperre für Ihr privates Objekt zu erhalten." - Was ist, wenn der "böswillige Benutzer" ein anderes Objekt im synchronizedBlock als das "Ihr privates Objekt" verwendet?
Arnobpl

36

Der Unterschied besteht darin, in welcher Sperre erworben wird:

  • Die synchronisierte Methode erhält eine Sperre für das gesamte Objekt. Dies bedeutet, dass kein anderer Thread eine synchronisierte Methode im gesamten Objekt verwenden kann, während die Methode von einem Thread ausgeführt wird.

  • synchronisierte Blöcke erhalten nach dem synchronisierten Schlüsselwort eine Sperre im Objekt zwischen Klammern. Dies bedeutet, dass kein anderer Thread eine Sperre für das gesperrte Objekt erhalten kann, bis der synchronisierte Block beendet wird.

Wenn Sie also das gesamte Objekt sperren möchten, verwenden Sie eine synchronisierte Methode. Wenn Sie andere Teile des Objekts für andere Threads zugänglich machen möchten, verwenden Sie den synchronisierten Block.

Wenn Sie das gesperrte Objekt sorgfältig auswählen, führen synchronisierte Blöcke zu weniger Konflikten, da das gesamte Objekt / die gesamte Klasse nicht blockiert wird.

Dies gilt ähnlich für statische Methoden: Eine synchronisierte statische Methode erhält eine Sperre für das gesamte Klassenobjekt, während ein synchronisierter Block innerhalb einer statischen Methode eine Sperre für das Objekt zwischen Klammern erhält.


Wenn ich also einen synchronisierten Block habe und ein Thread darin ausgeführt wird, kann ein anderer Thread in das Objekt gehen und einen synchronisierten Block an einer anderen Stelle im Objekt ausführen? Wenn ich eine synchronisierte Methode habe und ein Thread darin ausgeführt wird, kann kein anderer Thread in diesem Objekt oder nur in synchronisierten Bereichen des Objekts ausgeführt werden?
Böse Waschmaschine

6

Definieren Sie "besser". Ein synchronisierter Block ist nur deshalb besser, weil Sie Folgendes können:

  1. Synchronisieren Sie auf einem anderen Objekt
  2. Begrenzen Sie den Umfang der Synchronisation

Ihr spezielles Beispiel ist nun ein Beispiel für das doppelt überprüfte Sperrmuster , das verdächtig ist (in älteren Java-Versionen war es fehlerhaft und es ist leicht, es falsch zu machen).

Wenn Ihre Initialisierung billig ist, ist es möglicherweise besser, sofort mit einem letzten Feld zu initialisieren, und nicht bei der ersten Anforderung würde auch die Notwendigkeit einer Synchronisierung beseitigt.


5

Der Unterschied zwischen dem synchronisierten Block und der synchronisierten Methode ist folgender:

  1. Der synchronisierte Block reduziert den Sperrbereich, aber der Sperrbereich der synchronisierten Methode ist die gesamte Methode.
  2. Der synchronisierte Block weist eine bessere Leistung auf, da nur der kritische Abschnitt gesperrt ist , die synchronisierte Methode jedoch eine schlechtere Leistung als der Block aufweist.
  3. Der synchronisierte Block bietet eine detaillierte Kontrolle über die Sperre , die synchronisierte Methodensperre jedoch entweder für das aktuelle Objekt, das durch diese Sperre dargestellt wird, oder für die Sperre auf Klassenebene.
  4. Der synchronisierte Block kann eine NullPointerException auslösen , die synchronisierte Methode jedoch nicht.
  5. synchronisierter Block: synchronized(this){}

    synchronisierte Methode: public synchronized void fun(){}


3

synchronisiert sollte nur verwendet werden, wenn Ihre Klasse threadsicher sein soll. Tatsächlich sollten die meisten Klassen sowieso nicht synchronisiert verwenden. Die synchronisierte Methode würde nur eine Sperre für dieses Objekt und nur für die Dauer seiner Ausführung bereitstellen . Wenn Sie Ihre Klassen wirklich threadsicher machen möchten , sollten Sie in Betracht ziehen, Ihre Variablen flüchtig zu machen oder den Zugriff zu synchronisieren .

Eines der Probleme bei der Verwendung der synchronisierten Methode besteht darin, dass alle Mitglieder der Klasse dieselbe Sperre verwenden, wodurch Ihr Programm langsamer wird. In Ihrem Fall würden synchronisierte Methode und Block nicht anders ausgeführt. Ich würde empfehlen, ein dediziertes Schloss und einen synchronisierten Block wie diesen zu verwenden.

public class AClass {
private int x;
private final Object lock = new Object();     //it must be final!

 public void setX() {
    synchronized(lock) {
        x++;
    }
 }
}

1

In Ihrem Fall sind beide gleichwertig!

Das Synchronisieren einer statischen Methode entspricht einem synchronisierten Block für das entsprechende Klassenobjekt.

Wenn Sie eine synchronisierte statische Methodensperre deklarieren, wird auf dem Monitor, der dem Class-Objekt entspricht, eine Sperre erhalten.

public static synchronized int getCount() {
    // ...
}

ist das gleiche wie

public int getCount() {
    synchronized (ClassName.class) {
        // ...
    }
}

OK . Wenn ich eine nicht statische Methode synchronisiere, was wäre dann das Ergebnis
Sabapathy

Dann ist es pro Instanz oder das Objekt, das Sie erstellen. Zwei Threads können dieselbe Methode in zwei verschiedenen Objekten unabhängig voneinander verarbeiten.
Aniket Thakur

1

Da das Sperren teuer ist, sperren Sie bei Verwendung eines synchronisierten Blocks nur dann, wenn Sie _instance == nullnach der _instanceendgültigen Initialisierung niemals sperren. Wenn Sie jedoch eine Methode synchronisieren, werden Sie auch nach der _instanceInitialisierung bedingungslos gesperrt. Dies ist die Idee hinter dem doppelt überprüften Optimierungsmuster für Sperren http://en.wikipedia.org/wiki/Double-checked_locking .


1

Es sollte nicht als eine Frage der besten Verwendung angesehen werden, aber es hängt wirklich vom Anwendungsfall oder vom Szenario ab.

Synchronisierte Methoden

Eine gesamte Methode kann als synchronisiert markiert werden, was zu einer impliziten Sperre dieser Referenz (Instanzmethoden) oder Klasse (statische Methoden) führt. Dies ist ein sehr praktischer Mechanismus, um eine Synchronisation zu erreichen.

Schritte Ein Thread greift auf die synchronisierte Methode zu. Es erwirbt implizit die Sperre und führt den Code aus. Wenn ein anderer Thread auf die obige Methode zugreifen möchte, muss er warten. Der Thread kann die Sperre nicht erhalten, wird blockiert und muss warten, bis die Sperre aufgehoben wird.

Synchronisierte Blöcke

Um eine Sperre für ein Objekt für einen bestimmten Satz von Codeblöcken zu erhalten, sind synchronisierte Blöcke am besten geeignet. Da ein Block ausreicht, ist die Verwendung einer synchronisierten Methode eine Verschwendung.

Insbesondere mit Synchronized Block ist es möglich, die Objektreferenz zu definieren, für die eine Sperre erworben werden soll.


0

Ein klassischer Unterschied zwischen dem synchronisierten Block und der synchronisierten Methode besteht darin, dass die synchronisierte Methode das gesamte Objekt sperrt. Der synchronisierte Block sperrt nur den Code innerhalb des Blocks.

Synchronisierte Methode: Grundsätzlich deaktivieren diese beiden Synchronisierungsmethoden das Multithreading. Ein Thread vervollständigt also die Methode1 () und der andere Thread wartet auf die Fertigstellung von Thread1.

Klasse SyncExerciseWithSyncMethod {

public synchronized void method1() {
    try {
        System.out.println("In Method 1");
        Thread.sleep(5000);
    } catch (Exception e) {
        System.out.println("Catch of method 1");
    } finally {
        System.out.println("Finally of method 1");
    }

}

public synchronized void method2() {
    try {
        for (int i = 1; i < 10; i++) {
            System.out.println("Method 2 " + i);
            Thread.sleep(1000);
        }
    } catch (Exception e) {
        System.out.println("Catch of method 2");
    } finally {
        System.out.println("Finally of method 2");
    }
}

}}

Ausgabe

In Methode 1

Schließlich von Methode 1

Methode 2 1

Methode 2 2

Methode 2 3

Methode 2 4

Methode 2 5

Methode 2 6

Methode 2 7

Methode 2 8

Methode 2 9

Schließlich von Methode 2


Synchronisierter Block: Ermöglicht mehreren Threads den gleichzeitigen Zugriff auf dasselbe Objekt [Aktiviert Multithreading].

Klasse SyncExerciseWithSyncBlock {

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

public void method1() {
    synchronized (lock1) {
        try {
            System.out.println("In Method 1");
            Thread.sleep(5000);
        } catch (Exception e) {
            System.out.println("Catch of method 1");
        } finally {
            System.out.println("Finally of method 1");
        }
    }

}

public void method2() {

    synchronized (lock2) {
        try {
            for (int i = 1; i < 10; i++) {
                System.out.println("Method 2 " + i);
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            System.out.println("Catch of method 2");
        } finally {
            System.out.println("Finally of method 2");
        }
    }
}

}}

Ausgabe

In Methode 1

Methode 2 1

Methode 2 2

Methode 2 3

Methode 2 4

Methode 2 5

Schließlich von Methode 1

Methode 2 6

Methode 2 7

Methode 2 8

Methode 2 9

Schließlich von Methode 2

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.