409 Stimmen

Vermeiden Sie synchronized(this) in Java?

Wann immer auf SO eine Frage zur Java-Synchronisierung auftaucht, sind einige Leute sehr eifrig dabei, darauf hinzuweisen, dass synchronized(this) sollte vermieden werden. Stattdessen sei eine Sperre für eine private Referenz vorzuziehen.

Einige der genannten Gründe sind:

Andere Leute, darunter auch ich, argumentieren, dass synchronized(this) ist ein Idiom, das häufig verwendet wird (auch in Java-Bibliotheken), sicher ist und gut verstanden wird. Es sollte nicht vermieden werden, weil Sie einen Fehler haben und keine Ahnung haben, was in Ihrem Multithreading-Programm vor sich geht. Mit anderen Worten: Wenn es anwendbar ist, dann verwenden Sie es.

Ich bin daran interessiert, einige Beispiele aus der Praxis zu sehen (kein Foobar-Zeug), bei denen die Vermeidung einer Sperre auf this ist vorzuziehen, wenn synchronized(this) würde die Aufgabe ebenfalls erfüllen.

Deshalb: sollten Sie immer vermeiden synchronized(this) und sie durch eine Sperre für eine private Referenz ersetzen?


Einige weitere Informationen (aktualisiert, sobald Antworten gegeben werden):

  • es geht um die Synchronisierung von Instanzen
  • sowohl implizit ( synchronized Methoden) und explizite Form der synchronized(this) werden als
  • wenn Sie Bloch oder andere Autoritäten zu diesem Thema zitieren, lassen Sie nicht die Teile weg, die Ihnen nicht gefallen (z.B. Effektives Java, Artikel über Thread-Sicherheit: Normalerweise ist es die Sperre der Instanz selbst, aber es gibt Ausnahmen).
  • wenn Sie eine andere Granularität für Ihre Sperren benötigen als synchronized(this) bietet, dann synchronized(this) ist nicht anwendbar, also ist das nicht das Problem

4 Stimmen

Ich möchte auch darauf hinweisen, dass der Kontext wichtig ist - der Teil "Typischerweise ist es die Sperre der Instanz selbst" steht in einem Abschnitt über die Dokumentation einer bedingt thread-sicheren Klasse, wenn Sie die Sperre öffentlich machen. Mit anderen Worten, dieser Satz gilt, wenn Sie diese Entscheidung bereits getroffen haben.

0 Stimmen

In Ermangelung einer internen Synchronisierung und wenn eine externe Synchronisierung erforderlich ist, ist die Sperre oft die Instanz selbst, so Bloch. Warum sollte dies also nicht auch bei interner Synchronisation mit Sperre auf "this" der Fall sein? (Die Bedeutung der Dokumentation ist ein anderes Thema.)

0 Stimmen

Es besteht ein Kompromiss zwischen erweiterter Granularität und zusätzlichem CPU-Cache- und Busanforderungs-Overhead, da das Sperren eines externen Objekts höchstwahrscheinlich eine separate Cache-Zeile erfordert, die geändert und zwischen CPU-Caches ausgetauscht werden muss (vgl. MESIF und MOESI).

140voto

Darron Punkte 20861

Ich werde auf jeden Punkt einzeln eingehen.

  1. Ein böser Code kann Ihr Schloss stehlen (sehr beliebt, hat auch einen "zufällige" Variante)

    Ich mache mir mehr Sorgen über versehentlich . Es läuft darauf hinaus, dass diese Verwendung von this ist Teil der exponierten Schnittstelle Ihrer Klasse und sollte dokumentiert werden. Manchmal ist es erwünscht, dass anderer Code Ihre Sperre verwenden kann. Dies gilt für Dinge wie Collections.synchronizedMap (siehe das Javadoc).

  2. Alle synchronisierten Methoden innerhalb der gleichen Klasse verwenden genau die gleiche Sperre, was den Durchsatz verringert

    Dies ist eine allzu vereinfachte Sichtweise, denn es reicht aus, die synchronized(this) wird das Problem nicht lösen. Die richtige Synchronisierung für den Durchsatz erfordert mehr Überlegung.

  3. Sie geben (unnötigerweise) zu viele Informationen preis

    Dies ist eine Variante von Nr. 1. Verwendung von synchronized(this) ist Teil Ihrer Schnittstelle. Wenn Sie das nicht wollen/brauchen, tun Sie es nicht.

0 Stimmen

1. "synchronized" ist nicht Teil der exponierten Schnittstelle Ihrer Klasse. 2. stimme zu 3. siehe 1.

72 Stimmen

Im Wesentlichen synchronisiert(this) ist exponiert, weil es bedeutet, dass externer Code die Funktionsweise Ihrer Klasse beeinflussen kann. Daher behaupte ich, dass Sie sie als Schnittstelle dokumentieren müssen, auch wenn die Sprache dies nicht tut.

0 Stimmen

+1 Sie werden Punkt 2 los und ziehen 1 und 3 zusammen (andere haben das auch getan, scheint im Nachhinein offensichtlich), und sehr interessante Bemerkung. Diese hier bringt mich zum Nachdenken. Willst du damit sagen, dass es auf der gleichen Ebene liegt wie die Dokumentation eines NullPointerEx, da die Übergabe einer Null-Referenz die Funktion einer Methode beeinflussen kann?

92voto

cletus Punkte 596503

Nun, zunächst einmal sollte darauf hingewiesen werden, dass:

public void blah() {
  synchronized (this) {
    // do stuff
  }
}

ist semantisch äquivalent zu:

public synchronized void blah() {
  // do stuff
}

was ein Grund dafür ist, nicht die synchronized(this) . Man könnte argumentieren, dass man Dinge rund um das Haus tun kann. synchronized(this) blockieren. Der übliche Grund ist der Versuch, die synchronisierte Prüfung überhaupt zu vermeiden, was zu allen möglichen Gleichzeitigkeitsproblemen führt, insbesondere die Problem der doppelten Verriegelung was nur zeigt, wie schwierig es sein kann, eine relativ einfache Prüfung thread-sicher zu machen.

Eine private Sperre ist ein Verteidigungsmechanismus, der nie eine schlechte Idee ist.

Außerdem können, wie Sie bereits angedeutet haben, private Sperren die Granularität steuern. Ein Satz von Operationen an einem Objekt kann völlig unabhängig von einem anderen sein, aber synchronized(this) schließt den Zugriff auf alle von ihnen aus.

synchronized(this) gibt Ihnen wirklich nichts.

0 Stimmen

Wenn die Granularität eine Rolle spielt, ist synchronized(this) möglicherweise nicht anwendbar, das stimmt. Aber: die erforderliche Granularität ist sehr oft genau die Granularität, die von synchronisierten Instanzmethoden bereitgestellt wird, und wir sehen, dass synchronisierte Methoden in Java ziemlich häufig sind.

4 Stimmen

"synchronized(this) gibt einem wirklich nichts." Ok, ich ersetze es durch ein synchronize(myPrivateFinalLock). Was bringt mir das? Du sprichst davon, dass es ein Verteidigungsmechanismus ist. Wovor bin ich geschützt?

17 Stimmen

Sie sind gegen versehentliches (oder böswilliges) Sperren von "this" durch externe Objekte geschützt.

61voto

Andreas Bakurov Punkte 1475

Wenn Sie synchronized(this) verwenden, verwenden Sie die Klasseninstanz selbst als Sperre. Das bedeutet, dass während die Sperre von Gewinde 1 die Gewinde 2 warten sollte.

Nehmen wir den folgenden Code an:

public void method1() {
    // do something ...
    synchronized(this) {
        a ++;      
    }
    // ................
}

public void method2() {
    // do something ...
    synchronized(this) {
        b ++;      
    }
    // ................
}

Methode 1: Änderung der Variablen a und Methode 2 die Änderung der Variablen b sollte die gleichzeitige Änderung derselben Variablen durch zwei Threads vermieden werden, was auch geschieht. ABER während Gewinde1 Ändern von a y Thema2 Ändern von b es kann ohne jede Race Condition durchgeführt werden.

Leider lässt der obige Code dies nicht zu, da wir denselben Verweis für eine Sperre verwenden. Das bedeutet, dass Threads, auch wenn sie sich nicht in einer Race Condition befinden, warten müssen, und der Code opfert offensichtlich die Gleichzeitigkeit des Programms.

Die Lösung ist die Verwendung von 2 verschiedene Schlösser für zwei verschiedene Variablen:

public class Test {

    private Object lockA = new Object();
    private Object lockB = new Object();

    public void method1() {
        // do something ...
        synchronized(lockA) {
            a ++;      
        }
        // ................
    }

    public void method2() {
        // do something ...
        synchronized(lockB) {
            b ++;      
        }
        // ................
    }

}

Das obige Beispiel verwendet feiner abgestufte Sperren (2 Sperren statt einer ( lockA und sperrenB für Variablen a y b bzw.) und ermöglicht dadurch eine bessere Gleichzeitigkeit, andererseits wurde es komplexer als das erste Beispiel ...

0 Stimmen

Dies ist sehr gefährlich. Sie haben jetzt eine client-seitige (Benutzer dieser Klasse) Sperre Bestellung Anforderung eingeführt. Wenn zwei Threads Methode1() und Methode2() in unterschiedlicher Reihenfolge aufrufen, werden sie wahrscheinlich in eine Sackgasse geraten, aber der Benutzer dieser Klasse hat keine Ahnung, dass dies der Fall ist.

8 Stimmen

Granularität, die nicht durch "synchronized(this)" bereitgestellt wird, gehört nicht zu meiner Frage. Und sollten Ihre Sperrfelder nicht endgültig sein?

13 Stimmen

Um ein Deadlock zu haben, sollten wir einen Aufruf vom Block, der von A synchronisiert wird, zum Block, der von B synchronisiert wird, durchführen. daveb, du liegst falsch ...

15voto

Olivier Punkte 1222

Ich stimme zwar zu, dass man sich nicht blindlings an dogmatische Regeln halten sollte, aber erscheint Ihnen das Szenario des "Sperrdiebstahls" so exzentrisch? Ein Thread könnte in der Tat die Sperre für Ihr Objekt "von außen" erwerben ( synchronized(theObject) {...} ), wodurch andere Threads, die auf synchronisierte Instanzmethoden warten, blockiert werden.

Wenn Sie nicht an bösartigen Code glauben, bedenken Sie, dass dieser Code von Dritten stammen könnte (z. B. wenn Sie eine Art Anwendungsserver entwickeln).

Die "zufällige" Version scheint weniger wahrscheinlich, aber wie man so schön sagt: "Wenn man etwas idiotensicher macht, erfindet man einen besseren Idioten".

Ich stimme also mit der Denkweise überein, dass es davon abhängt, was die Klasse tut.


Edit nach den ersten 3 Kommentaren von eljenso:

Ich habe das Problem des Diebstahls von Schlössern noch nie erlebt, aber hier ist ein imaginäres Szenario:

Angenommen, Ihr System ist ein Servlet-Container, und das Objekt, das wir betrachten, ist die ServletContext Umsetzung. Seine getAttribute Methode muss thread-sicher sein, da Kontextattribute gemeinsam genutzte Daten sind; daher deklarieren Sie sie als synchronized . Stellen wir uns außerdem vor, dass Sie einen öffentlichen Hosting-Dienst auf der Grundlage Ihrer Container-Implementierung anbieten.

Ich bin Ihr Kunde und setze mein "gutes" Servlet auf Ihrer Website ein. Es kommt vor, dass mein Code einen Aufruf enthält an getAttribute .

Ein Hacker, getarnt als ein anderer Kunde, setzt sein bösartiges Servlet auf Ihrer Website ein. Es enthält den folgenden Code in der init Methode:

synchronized (this.getServletConfig().getServletContext()) {
   while (true) {}
}

Unter der Annahme, dass wir uns denselben Servlet-Kontext teilen (was nach der Spezifikation zulässig ist, solange sich die beiden Servlets auf demselben virtuellen Host befinden), kann mein Aufruf auf getAttribute ist für immer gesperrt. Der Hacker hat einen DoS auf mein Servlet erreicht.

Dieser Angriff ist nicht möglich, wenn getAttribute wird mit einer privaten Sperre synchronisiert, da Code von Drittanbietern diese Sperre nicht erwerben kann.

Ich gebe zu, dass das Beispiel konstruiert ist und eine zu einfache Sichtweise auf die Funktionsweise eines Servlet-Containers darstellt, aber IMHO beweist es den Punkt.

Ich würde also meine Design-Entscheidung auf der Grundlage von Sicherheitsüberlegungen treffen: Werde ich die vollständige Kontrolle über den Code haben, der Zugriff auf die Instanzen hat? Was wäre die Konsequenz, wenn ein Thread eine Instanz auf unbestimmte Zeit sperren würde?

0 Stimmen

Es hängt davon ab, was die Klasse tut: Wenn es sich um ein "wichtiges" Objekt handelt, dann auf eine private Referenz sperren? Andernfalls reicht die Instanzsperre aus?

7 Stimmen

Ja, das Szenario des Schlossdiebstahls erscheint mir weit hergeholt. Alle erwähnen es, aber wer hat es tatsächlich getan oder erlebt? Wenn Sie "versehentlich" ein Objekt sperren, das Sie nicht sperren sollten, dann gibt es einen Namen für diese Art von Situation: Es ist ein Fehler. Beheben Sie ihn.

4 Stimmen

Auch das Sperren von internen Referenzen ist nicht frei von der "externen Synch-Attacke": Wenn Sie wissen, dass ein bestimmter synchronisierter Teil des Codes auf ein externes Ereignis wartet (z.B. Dateischreiben, Wert in der DB, Timer-Ereignis), können Sie wahrscheinlich dafür sorgen, dass er ebenfalls blockiert wird.

14voto

Rohit Singh Punkte 14318

Das hängt von der jeweiligen Situation ab.
Wenn es nur eine oder mehrere geteilte Einheiten gibt.

Siehe vollständiges Arbeitsbeispiel hier

Eine kleine Einführung.

Threads und gemeinsam nutzbare Einheiten
Es ist möglich, dass mehrere Threads auf dieselbe Einheit zugreifen, z. B. mehrere connectionThreads, die sich eine einzige messageQueue teilen. Da die Threads gleichzeitig ablaufen, besteht die Möglichkeit, dass die Daten eines Threads von einem anderen überschrieben werden, was zu einem Durcheinander führen kann.
Wir brauchen also eine Möglichkeit, um sicherzustellen, dass nur jeweils ein Thread auf eine gemeinsam nutzbare Entität zugreifen kann. (CONCURRENCY).

Synchronisierter Block
synchronized()-Block ist eine Möglichkeit, den gleichzeitigen Zugriff auf eine gemeinsam nutzbare Entität zu gewährleisten.
Zunächst eine kleine Analogie
Angenommen, es gibt zwei Personen P1, P2 (Fäden), ein Waschbecken (gemeinsam nutzbare Einheit) in einem Waschraum und eine Tür (Schloss).
Jetzt wollen wir, dass jeweils eine Person das Waschbecken benutzt.
Ein Ansatz besteht darin, die Tür durch P1 zu verschließen, wenn die Tür verschlossen ist, wartet P2, bis P1 seine Arbeit beendet hat
P1 entriegelt die Tür
dann kann nur p1 das Waschbecken benutzen.

Syntax.

synchronized(this)
{
  SHARED_ENTITY.....
}

"this" stellt die der Klasse zugeordnete Sperre zur Verfügung (Java-Entwickler haben die Klasse Object so konzipiert, dass jedes Objekt als Monitor arbeiten kann). Der obige Ansatz funktioniert gut, wenn es nur eine gemeinsame Entität und mehrere Threads gibt (1: N).
enter image description here N gemeinsam nutzbare Einheiten - M Threads
Stellen Sie sich nun eine Situation vor, in der es in einem Waschraum zwei Waschbecken und nur eine Tür gibt. Wenn wir den vorherigen Ansatz verwenden, kann nur p1 jeweils ein Waschbecken benutzen, während p2 draußen wartet. Das ist eine Verschwendung von Ressourcen, da niemand B2 (Waschbecken) benutzt.
Ein klügerer Ansatz wäre es, einen kleineren Raum im Waschraum zu schaffen und ihnen eine Tür pro Waschbecken zu geben. Auf diese Weise kann P1 Zugang zu B1 und P2 Zugang zu B2 haben und umgekehrt.

washbasin1;  
washbasin2;

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

  synchronized(lock1)
  {
    washbasin1;
  }

  synchronized(lock2)
  {
    washbasin2;
  }

enter image description here
enter image description here

Siehe mehr auf Threads ----> hier

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X