it-swarm.com.de

Vermeiden Sie (dies) in Java synchronisiert?

Immer wenn eine Frage zu SO= about Java= Synchronisation) auftaucht, möchten einige Leute unbedingt darauf hinweisen, dass synchronized(this) vermieden werden sollte , so behaupten sie, sei eine Sperre für eine private Referenz vorzuziehen.

Einige der angegebenen Gründe sind:

Andere Leute, einschließlich mir, argumentieren, dass synchronized(this) eine Redewendung ist, die häufig verwendet wird (auch in Java) -Bibliotheken), sicher und gut verstanden ist. Es sollte nicht vermieden werden weil Sie einen Fehler haben und keine Ahnung haben, was in Ihrem Multithread-Programm vor sich geht. Mit anderen Worten: Wenn es anwendbar ist, verwenden Sie es.

Ich bin daran interessiert, einige Beispiele aus der Praxis zu sehen (keine Dummköpfe), bei denen das Vermeiden einer Sperre für this vorzuziehen ist, wenn synchronized(this) ebenfalls die Aufgabe übernehmen würde.

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


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

  • wir sprechen über Instanzsynchronisation
  • es werden sowohl implizite (synchronized Methoden) als auch explizite Formen von synchronized(this) berücksichtigt
  • wenn Sie zu diesem Thema Bloch oder andere Autoritäten zitieren, lassen Sie die Teile, die Sie nicht mögen, nicht aus (z. B. Effektives Java, Element zur Threadsicherheit: In der Regel ist es die Sperre für die Instanz selbst, aber es gibt sie Ausnahmen.)
  • wenn Sie eine andere Granularität für die Sperrung benötigen als die von synchronized(this) bereitgestellte, ist synchronized(this) nicht anwendbar, sodass dies nicht der Fall ist
362
eljenso

Ich werde jeden Punkt separat behandeln.

  1. Ein böser Code kann Ihr Schloss stehlen (sehr beliebt, hat auch eine "versehentliche" Variante)

    Ich mache mir mehr Sorgen um aus Versehen. Was es bedeutet, ist, dass diese Verwendung von this Teil der exponierten Schnittstelle Ihrer Klasse ist und dokumentiert werden sollte. Manchmal ist die Fähigkeit eines anderen Codes zur Verwendung Ihres Schlosses erwünscht. Dies gilt für Dinge wie Collections.synchronizedMap (Siehe Javadoc).

  2. Alle synchronisierten Methoden innerhalb derselben Klasse verwenden genau dieselbe Sperre, wodurch der Durchsatz verringert wird

    Das ist zu simpel gedacht; Nur synchronized(this) loszuwerden, löst das Problem nicht. Die richtige Synchronisation für den Durchsatz erfordert mehr Überlegungen.

  3. Sie legen (unnötigerweise) zu viele Informationen offen

    Dies ist eine Variante von # 1. Die Verwendung von synchronized(this) ist Teil Ihrer Benutzeroberfläche. Wenn Sie dies nicht möchten/brauchen, tun Sie es nicht.

124
Darron

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

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

ist semantisch äquivalent zu:

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

dies ist ein Grund, warum Sie synchronized(this) nicht verwenden sollten. Sie könnten argumentieren, dass Sie Dinge um den Block synchronized(this) tun können. Der übliche Grund ist, zu versuchen und zu vermeiden, dass die synchronisierte Prüfung überhaupt durchgeführt werden muss, was zu allen möglichen Problemen im Zusammenhang mit dem gemeinsamen Zugriff führt, insbesondere zum Problem der doppelten überprüften Sperrung , was nur zeigt, wie schwierig dies sein kann sei es, eine relativ einfache Prüfung threadsicher zu machen.

Eine private Sperre ist ein Abwehrmechanismus, was niemals eine schlechte Idee ist.

Wie Sie bereits angedeutet haben, können private Sperren die Granularität steuern. Eine Gruppe von Operationen an einem Objekt hat möglicherweise keinerlei Beziehung zu einer anderen, aber synchronized(this) schließt den Zugriff auf alle gegenseitig aus.

synchronized(this) gibt dir einfach wirklich nichts.

84
cletus

Während Sie synchronized (this) verwenden, verwenden Sie die Klasseninstanz selbst als Sperre. Dies bedeutet, dass, während die Sperre von Thread 1 erfasst wird, Thread 2 warten sollte.

Angenommen, der folgende Code:

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


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

Methode 1 zum Ändern der Variablen a und Methode 2 zum Ändern der Variablen b. Die gleichzeitige Änderung derselben Variablen durch zwei Threads sollte vermieden werden. ABER während thread1 modifiziert a und thread2 modifiziert b kann es ohne Racebedingung durchgeführt werden.

Leider lässt der obige Code dies nicht zu, da wir dieselbe Referenz für ein Schloss verwenden. Dies bedeutet, dass Threads warten sollten, auch wenn sie sich nicht in einem Race-Zustand befinden, und der Code offensichtlich die Parallelität des Programms opfert.

Die Lösung besteht darin, 2 verschiedene Sperren für zwei verschiedene Variablen zu verwenden:

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

}

Im obigen Beispiel werden feinkörnigere Sperren verwendet (2 Sperren statt einer (lockA und lockB für Variablen a und b bzw.) und ermöglicht dadurch eine bessere Parallelität, andererseits wurde es komplexer als das erste Beispiel ...

52
Andreas Bakurov

Während ich damit einverstanden bin, dass ich mich nicht blind an dogmatische Regeln halte, scheint Ihnen das Szenario des "Schlossdiebstahls" so exzentrisch zu sein? Ein Thread könnte tatsächlich die Sperre für Ihr Objekt "extern" (synchronized(theObject) {...}) erlangen und andere Threads blockieren, die auf synchronisierte Instanzmethoden warten.

Wenn Sie nicht an böswilligen Code glauben, können Sie davon ausgehen, dass dieser Code von Dritten stammt (z. B. wenn Sie einen Anwendungsserver entwickeln).

Die "versehentliche" Version scheint weniger wahrscheinlich, aber wie sie sagen, "mach etwas idiotensicher und jemand wird einen besseren Idioten erfinden".

Also stimme ich der Meinung zu, dass es davon abhängt, was die Klasse denkt.


Bearbeite die ersten 3 Kommentare von eljenso:

Ich habe das Problem des Schlossdiebstahls noch nie erlebt, aber hier ein imaginäres Szenario:

Angenommen, Ihr System ist ein Servlet-Container, und wir betrachten als Objekt die Implementierung ServletContext. Die getAttribute -Methode muss threadsicher sein, da Kontextattribute gemeinsam genutzte Daten sind. Also deklarieren Sie es als synchronized. Stellen wir uns auch vor, Sie stellen einen Public-Hosting-Service bereit, der auf Ihrer Container-Implementierung basiert.

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

Ein als anderer Kunde getarnter Hacker stellt sein bösartiges Servlet auf Ihrer Site bereit. Es enthält den folgenden Code in der init -Methode:

 synchronisiert (this.getServletConfig (). getServletContext ()) {
 während (true) {} 
} 

Angenommen, wir teilen denselben Servlet-Kontext (gemäß Spezifikation zulässig, solange sich die beiden Servlets auf demselben virtuellen Host befinden), ist mein Aufruf von getAttribute für immer gesperrt. Der Hacker hat auf meinem Servlet einen DoS erreicht.

Dieser Angriff ist nicht möglich, wenn getAttribute für eine private Sperre synchronisiert ist, da der Code eines Drittanbieters diese Sperre nicht abrufen kann.

Ich gebe zu, dass das Beispiel erfunden und die Funktionsweise eines Servlet-Containers stark vereinfacht ist, aber meiner Meinung nach beweist es den Sinn.

Daher würde ich meine Entwurfsentscheidung aus Sicherheitsgründen treffen: Habe ich die vollständige Kontrolle über den Code, der Zugriff auf die Instanzen hat? Was wäre die Folge davon, dass ein Thread eine Instanz auf unbestimmte Zeit sperrt?

14
Olivier

Es scheint einen anderen Konsens in den C # - und Java) -Camps zu geben. Die Mehrheit des Java) -Codes, den ich gesehen habe, verwendet:

// apply mutex to this instance
synchronized(this) {
    // do work here
}

in der Erwägung, dass die Mehrheit des C # -Codes das wohl sicherere wählt:

// instance level lock object
private readonly object _syncObj = new object();

...

// apply mutex to private instance level field (a System.Object usually)
lock(_syncObj)
{
    // do work here
}

Die C # -Idiom ist sicherlich sicherer. Wie bereits erwähnt, kann von außerhalb der Instanz kein böswilliger/versehentlicher Zugriff auf das Schloss erfolgen. Java Code hat auch dieses Risiko, aber es scheint, dass die Java Community im Laufe der Zeit etwas weniger sicher, aber etwas knapper geworden ist Version.

Das ist nicht als Ausgrabung gegen Java gedacht, sondern nur eine Widerspiegelung meiner Erfahrung mit der Arbeit in beiden Sprachen.

11
serg10

Es hängt von der Situation ab.
Wenn es nur eine gemeinsame Entität gibt oder mehr als eine.

Siehe vollständiges Arbeitsbeispiel hier

Eine kleine Einführung.

Threads und gemeinsam nutzbare Einheiten
Es ist möglich, dass mehrere Threads auf dieselbe Entität zugreifen, z. B. wenn mehrere connectionThreads eine MessageQueue gemeinsam nutzen. Da die Threads gleichzeitig ausgeführt werden, besteht möglicherweise die Möglichkeit, dass die Daten durch andere überschrieben werden, was möglicherweise eine durcheinandergebrachte Situation ist.
Wir müssen also sicherstellen, dass auf eine gemeinsam nutzbare Entität jeweils nur ein Thread zugreifen kann. (KONKURRENZ).

Synchronisierter Block
synchronized () Block ist eine Möglichkeit, den gleichzeitigen Zugriff auf eine gemeinsam nutzbare Entität sicherzustellen.
Zunächst eine kleine Analogie
Angenommen, in einem Waschraum befindet sich ein Waschbecken (gemeinsam nutzbare Einheit) für zwei Personen (P1, P2) und eine Tür (Schloss).
Nun möchten wir, dass jeweils eine Person das Waschbecken benutzt.
Ein Ansatz besteht darin, die Tür durch P1 zu verriegeln, wenn die Tür verriegelt ist. P2 wartet, bis p1 seine Arbeit beendet hat
P1 entriegelt die Tür
dann kann nur p1 waschbecken benutzen.

syntax.

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

"this" stellte die der Klasse zugeordnete interne Sperre bereit (Java-Entwickler hat die Object-Klasse so entworfen, dass jedes Objekt als Monitor fungieren kann). Der obige Ansatz funktioniert einwandfrei, wenn nur eine gemeinsame Entität und mehrere Threads vorhanden sind (1: N).
enter image description here N gemeinsam nutzbare Entities-M-Threads
Stellen Sie sich nun eine Situation vor, in der sich zwei Waschbecken in einem Waschraum und nur eine Tür befinden. Wenn wir den vorherigen Ansatz verwenden, kann nur p1 jeweils ein Waschbecken verwenden, während p2 draußen wartet. Es ist eine Verschwendung von Ressourcen, da niemand B2 (Waschbecken) benutzt.
Ein klügerer Ansatz wäre, einen kleineren Raum im Waschraum zu schaffen und ihnen eine Tür pro Waschbecken zur Verfügung zu stellen. Auf diese Weise kann P1 auf B1 zugreifen und P2 kann auf B2 zugreifen 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

Weitere Informationen zu Threads ----> hier

10
Rohit Singh

Das Paket Java.util.concurrent Hat die Komplexität meines thread-sicheren Codes erheblich reduziert. Ich habe nur anekdotische Beweise, aber die meisten Arbeiten, die ich mit synchronized(x) gesehen habe, scheinen eine Sperre, ein Semaphor oder einen Latch neu zu implementieren, verwenden aber die untergeordneten Monitore.

In diesem Sinne ist die Synchronisierung mit einem dieser Mechanismen analog zur Synchronisierung mit einem internen Objekt, anstatt eine Sperre zu löschen. Dies hat den Vorteil, dass Sie absolut sicher sind, dass Sie den Eintritt in den Monitor durch zwei oder mehr Threads steuern.

7
jamesh
  1. Machen Sie Ihre Daten unveränderlich, wenn es möglich ist (final Variablen)
  2. Wenn Sie die Mutation von gemeinsam genutzten Daten über mehrere Threads nicht vermeiden können, verwenden Sie übergeordnete Programmierkonstrukte [z. granulares Lock API]

Eine Sperre bietet exklusiven Zugriff auf eine gemeinsam genutzte Ressource: Es kann immer nur ein Thread die Sperre erwerben, und für den gesamten Zugriff auf die gemeinsam genutzte Ressource muss die Sperre zuerst erworben werden.

Beispielcode zur Verwendung von ReentrantLock, der die Schnittstelle Lock implementiert

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

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

Vorteile von Lock gegenüber Synchronized (this)

  1. Die Verwendung synchronisierter Methoden oder Anweisungen erzwingt die blockweise Erfassung und Freigabe aller Sperren.

  2. Sperrenimplementierungen bieten zusätzliche Funktionen gegenüber der Verwendung synchronisierter Methoden und Anweisungen, indem sie bereitgestellt werden

    1. Ein nicht blockierender Versuch, eine Sperre zu erlangen (tryLock())
    2. Ein Versuch, die Sperre abzurufen, die unterbrochen werden kann (lockInterruptibly())
    3. Ein Versuch, die Sperre abzurufen, bei der eine Zeitüberschreitung auftreten kann (tryLock(long, TimeUnit)).
  3. Eine Lock-Klasse kann auch ein Verhalten und eine Semantik bereitstellen, die sich stark von der impliziten Monitorsperre unterscheiden, z

    1. garantierte Bestellung
    2. nicht-Wiedereinsteiger-Nutzung
    3. Deadlock-Erkennung

Schauen Sie sich diese SE-Frage zu verschiedenen Arten von Locks an:

Synchronisation vs Sperre

Sie können Threadsicherheit erzielen, indem Sie anstelle von synchronisierten Blöcken die erweiterte Parallelitäts-API verwenden. Diese Dokumentation Seite bietet gute Programmierkonstrukte, um Threadsicherheit zu erreichen.

Sperrobjekte unterstützen Sperr-IDiome, die viele gleichzeitige Anwendungen vereinfachen.

Executors definieren eine übergeordnete API zum Starten und Verwalten von Threads. Von Java.util.concurrent bereitgestellte Executor-Implementierungen bieten Thread-Pool-Management, das für umfangreiche Anwendungen geeignet ist.

Concurrent Collections Erleichtert die Verwaltung großer Datensammlungen und kann den Synchronisationsbedarf erheblich verringern.

Atomare Variablen haben Funktionen, die die Synchronisation minimieren und dabei helfen, Fehler bei der Speicherkonsistenz zu vermeiden.

ThreadLocalRandom (in JDK 7) ermöglicht die effiziente Erzeugung von Pseudozufallszahlen aus mehrere Threads.

Informationen zu anderen Programmierkonstrukten finden Sie auch in den Paketen Java.util.concurrent und Java.util.concurrent.atomic .

6
Ravindra babu

Wenn Sie das entschieden haben:

  • sie müssen lediglich das aktuelle Objekt sperren. und
  • sie möchten es mit einer Granularität sperren, die kleiner als eine ganze Methode ist.

dann sehe ich kein tabu über synchronizezd (dies).

Einige Leute verwenden absichtlich synchronized (this) (anstatt die Methode synchronized zu markieren) innerhalb des gesamten Inhalts einer Methode, weil sie denken, es sei "für den Leser klarer", auf welchem ​​Objekt tatsächlich synchronisiert wird. Solange die Leute eine informierte Entscheidung treffen (z. B. verstehen, dass sie auf diese Weise zusätzliche Bytecodes in die Methode einfügen und dies einen negativen Einfluss auf mögliche Optimierungen haben könnte), sehe ich darin kein besonderes Problem . Sie sollten immer das gleichzeitige Verhalten Ihres Programms dokumentieren, damit ich nicht sehe, dass das Argument "synchronisiert" das Verhalten veröffentlicht "so überzeugend ist.

In Bezug auf die Frage, welche Objektsperre Sie verwenden sollten, ist meines Erachtens nichts Falsches daran, das aktuelle Objekt zu synchronisieren , wenn dies von der Logik Ihres Handelns und Ihrer Klasse erwartet wird würde normalerweise verwendet werden . Bei einer Auflistung ist das Objekt, von dem Sie logischerweise erwarten würden, dass es gesperrt wird, im Allgemeinen die Auflistung selbst.

5
Neil Coffey

Ich denke, es gibt eine gute Erklärung dafür, warum all dies wichtige Techniken sind, in einem Buch mit dem Titel Java Concurrency In Practice von Brian Goetz. Er macht einen Punkt sehr klar - Sie müssen das verwenden Die gleiche Sperre "ÜBERALL", um den Status Ihres Objekts zu schützen. Die synchronisierte Methode und die Synchronisierung auf einem Objekt gehen oft Hand in Hand. ZB synchronisiert Vector alle Methoden. Wenn Sie ein Handle für ein Vektorobjekt haben und "put if" ausführen möchten abwesend ", dann schützt Sie lediglich die Vektorsynchronisierung der eigenen Methoden nicht vor einer Beschädigung des Zustands. Sie müssen mit synchronized (vectorHandle) synchronisieren. Dies führt dazu, dass die SAME-Sperre von jedem Thread abgerufen wird, der ein Handle für das hat vector und schützt den Gesamtzustand des Vektors Dies wird als clientseitiges Sperren bezeichnet. Wir wissen, dass vector alle seine Methoden synchronisiert (this)/synchronisiert, und daher führt die Synchronisierung auf dem Objekt vectorHandle zu einer ordnungsgemäßen Synchronisierung von Vektorobjekten Zustand. Es ist töricht zu glauben, dass Sie threadsicher sind, nur weil Sie eine threadsichere Sammlung verwenden. Dies ist genau der Grund, warum ConcurrentHashMap die putIfAbsent-Methode explizit eingeführt hat, um solche Operationen atomar zu machen.

In Summe

  1. Das Synchronisieren auf Methodenebene ermöglicht das clientseitige Sperren.
  2. Wenn Sie ein privates Sperrobjekt haben, wird das clientseitige Sperren unmöglich. Dies ist in Ordnung, wenn Sie wissen, dass Ihre Klasse keine Funktionalität vom Typ "Setzen, wenn nicht vorhanden" hat.
  3. Wenn Sie eine Bibliothek entwerfen, ist die Synchronisierung auf dieser oder die Synchronisierung der Methode oft klüger. Weil Sie selten entscheiden können, wie Ihre Klasse verwendet wird.
  4. Hätte Vector ein privates Sperrobjekt verwendet, wäre es unmöglich gewesen, das richtige Ergebnis zu erzielen. Der Client-Code erhält niemals einen Zugriff auf die private Sperre und verstößt damit gegen die Grundregel, die EXACT SAME LOCK zum Schutz ihres Zustands zu verwenden.
  5. Die Synchronisierung auf diese oder synchronisierte Methoden hat ein Problem, wie andere bereits betont haben - jemand könnte eine Sperre erhalten und sie niemals freigeben. Alle anderen Threads warten darauf, dass die Sperre aufgehoben wird.
  6. Also wissen Sie, was Sie tun, und übernehmen Sie das Richtige.
  7. Jemand argumentierte, dass ein privates Sperrobjekt eine bessere Granularität bietet - z. Wenn zwei Vorgänge nicht zusammenhängen, können sie durch unterschiedliche Sperren geschützt werden, was zu einem besseren Durchsatz führt. Aber ich denke, das ist Designgeruch und nicht Code-Geruch - wenn zwei Operationen völlig unabhängig sind, warum sind sie Teil der gleichen Klasse? Warum sollte ein Klassenclub überhaupt unabhängige Funktionen haben? Kann eine Gebrauchsklasse sein? Hmmmm - einige nutzen die String-Manipulation und die Formatierung des Kalenderdatums über dieselbe Instanz? ... ergibt zumindest für mich keinen Sinn !!
4
Sandesh Sadhale

Nein, Sie sollten nicht immer . Ich neige jedoch dazu, dies zu vermeiden, wenn ein bestimmtes Objekt mehrere Probleme aufweist, die nur in Bezug auf sich selbst threadsicher sein müssen. Beispielsweise könnten Sie ein veränderbares Datenobjekt haben, das die Felder "label" und "parent" enthält. Diese müssen threadsicher sein, aber das Ändern eines muss nicht das Schreiben/Lesen des anderen blockieren. (In der Praxis würde ich dies vermeiden, indem ich die Felder als flüchtig deklariere und/oder die AtomicFoo-Wrapper von Java.util.concurrent verwende.).

Die Synchronisierung ist im Allgemeinen etwas umständlich, da sie eine große Sperre aufhebt, anstatt genau zu überlegen, wie Threads möglicherweise umeinander arbeiten dürfen. Die Verwendung von synchronized(this) ist noch unbeholfener und unsozialer, da es heißt, dass niemand an dieser Klasse etwas ändern darf , solange ich dies halte das Schloss". Wie oft müssen Sie das tatsächlich tun?

Ich hätte viel lieber granularere Sperren. Selbst wenn Sie verhindern möchten, dass sich alles ändert (vielleicht serialisieren Sie das Objekt), können Sie einfach alle Sperren abrufen, um dasselbe zu erreichen, und es wird auf diese Weise expliziter. Wenn Sie synchronized(this) verwenden, ist nicht klar, warum Sie synchronisieren oder welche Nebenwirkungen auftreten können. Wenn Sie synchronized(labelMonitor) oder noch besser labelLock.getWriteLock().lock() verwenden, ist klar, was Sie tun und worauf die Auswirkungen Ihres kritischen Abschnitts beschränkt sind.

3
Andrzej Doyle

kurze Antwort: Sie müssen den Unterschied verstehen und je nach Code eine Auswahl treffen.

Lange Antwort: Im Allgemeinen würde ich lieber vermeiden , (dies) zu synchronisieren , um Konflikte zu reduzieren, aber private Sperren erhöhen die Komplexität, die Sie haben sich bewusst sein über. Verwenden Sie also die richtige Synchronisation für den richtigen Job. Wenn Sie mit Multithread-Programmierung nicht so vertraut sind, halte ich mich lieber an das Sperren von Instanzen und lese dieses Thema nach. (Das heißt: Nur mit synchronisieren (dies) macht Ihre Klasse nicht automatisch vollständig thread-sicher.) Dies ist kein einfaches Thema, aber wenn Sie es einmal getan haben gewöhn dich dran, die antwort ob man synchronisiert (dies) oder nicht kommt natürlich.

3
tcurdt

Eine Sperre wird entweder verwendet, um Visibility oder um einige Daten vor Concurrent Modification zu schützen, was zu Rennen führen kann.

Wenn Sie nur primitive Typoperationen als atomar definieren müssen, stehen Optionen wie AtomicInteger und dergleichen zur Verfügung.

Angenommen, Sie haben zwei Ganzzahlen, die wie x- und y-Koordinaten miteinander in Beziehung stehen, die miteinander in Beziehung stehen und atomar geändert werden sollten. Dann würden Sie sie mit demselben Schloss schützen.

Eine Sperre soll nur den Zustand schützen, der miteinander in Beziehung steht. Nicht weniger und nicht mehr. Wenn Sie synchronized(this) in jeder Methode verwenden, werden auch dann, wenn der Status der Klasse nicht in Beziehung steht, alle Threads in Konflikt geraten, selbst wenn der nicht in Beziehung stehende Status aktualisiert wird.

class Point{
   private int x;
   private int y;

   public Point(int x, int y){
       this.x = x;
       this.y = y;
   }

   //mutating methods should be guarded by same lock
   public synchronized void changeCoordinates(int x, int y){
       this.x = x;
       this.y = y;
   }
}

Im obigen Beispiel habe ich nur eine Methode, die sowohl x als auch y mutiert und nicht zwei verschiedene Methoden, wie x und y, verwandt sind und wenn ja Wenn zwei verschiedene Methoden zum getrennten Mutieren von x und y gegeben worden wären, wäre dies nicht threadsicher gewesen.

Dieses Beispiel dient nur der Veranschaulichung und nicht unbedingt der Implementierung. Der beste Weg, dies zu tun, wäre es, es unveränderlich zu machen [~ # ~] [~ # ~] .

Im Gegensatz zum Beispiel Point gibt es ein Beispiel für TwoCounters, das bereits von @Andreas bereitgestellt wurde, wobei der Status, der durch zwei verschiedene Sperren geschützt wird, da der Status nicht in Beziehung zueinander steht.

Der Prozess der Verwendung verschiedener Sperren zum Schutz nicht verwandter Zustände wird Lock Striping oder Lock Splitting genannt

2
Narendra Pathai

Wie bereits erwähnt, kann der synchronisierte Block eine benutzerdefinierte Variable als Sperrobjekt verwenden, wenn die synchronisierte Funktion nur "this" verwendet. Und natürlich können Sie mit Bereichen Ihrer Funktion manipulieren, die synchronisiert werden sollen und so weiter.

Aber jeder sagt, dass es keinen Unterschied zwischen synchronisierter Funktion und Block gibt, der die gesamte Funktion abdeckt, wenn "this" als Sperrobjekt verwendet wird. Das ist nicht wahr, der Unterschied liegt im Byte-Code, der in beiden Situationen generiert wird. Im Falle einer synchronisierten Blockverwendung sollte eine lokale Variable zugewiesen werden, die einen Verweis auf "this" enthält. Und als Ergebnis haben wir eine etwas größere Funktionsgröße (nicht relevant, wenn Sie nur wenige Funktionen haben).

Eine ausführlichere Erklärung des Unterschieds finden Sie hier: http://www.artima.com/insidejvm/ed2/threadsynchP.html

Auch die Verwendung von synchronisierten Blöcken ist aus folgender Sicht nicht gut:

Das synchronisierte Schlüsselwort ist in einem Bereich sehr begrenzt: Beim Verlassen eines synchronisierten Blocks müssen alle Threads, die auf diese Sperre warten, entsperrt werden, aber nur einer dieser Threads kann die Sperre übernehmen. Alle anderen sehen, dass das Schloss geschlossen ist und kehren in den gesperrten Zustand zurück. Das ist nicht nur eine Menge verschwendeter Verarbeitungszyklen: Oft beinhaltet der Kontextwechsel zum Entsperren eines Threads auch das Auslagern von Arbeitsspeicher von der Festplatte, und das ist sehr, sehr teuer.

Für weitere Details in diesem Bereich würde ich empfehlen, diesen Artikel zu lesen: http://Java.dzone.com/articles/synchronized-considered

1
Roman

Der Grund für die Nicht-Synchronisierung bei this ist, dass Sie manchmal mehr als eine Sperre benötigen (die zweite Sperre wird oft nach einigem Nachdenken entfernt, aber Sie benötigen sie immer noch im Zwischenzustand). Wenn Sie this sperren, müssen Sie sich immer merken, welches der beiden Sperren this ist. Wenn Sie ein privates Objekt sperren, sagt Ihnen der Variablenname dies.

Wenn Sie aus der Sicht des Lesers feststellen, dass this aktiviert ist, müssen Sie immer die beiden folgenden Fragen beantworten:

  1. welche Art von Zugriff ist durch this geschützt?
  2. ist ein schloss wirklich genug, hat nicht jemand einen fehler eingeschleust?

Ein Beispiel:

class BadObject {
    private Something mStuff;
    synchronized setStuff(Something stuff) {
        mStuff = stuff;
    }
    synchronized getStuff(Something stuff) {
        return mStuff;
    }
    private MyListener myListener = new MyListener() {
        public void onMyEvent(...) {
            setStuff(...);
        }
    }
    synchronized void longOperation(MyListener l) {
        ...
        l.onMyEvent(...);
        ...
    }
}

Wenn zwei Threads an zwei verschiedenen Instanzen von BadObject mit longOperation() beginnen, erhalten sie ihre Sperren. Wenn es Zeit ist, l.onMyEvent(...) aufzurufen, ist ein Deadlock aufgetreten, da keiner der Threads die Sperre des anderen Objekts erlangen kann.

In diesem Beispiel können wir den Deadlock beseitigen, indem wir zwei Sperren verwenden, eine für kurze Operationen und eine für lange.

Dies ist wirklich nur eine Ergänzung zu den anderen Antworten. Wenn Sie jedoch hauptsächlich Einwände dagegen haben, private Objekte zum Sperren zu verwenden, ist dies, dass Ihre Klasse mit Feldern überfüllt ist, die nicht mit der Geschäftslogik zusammenhängen, dann hat Project Lombok @Synchronized um das Boilerplate beim Kompilieren zu generieren:

@Synchronized
public int foo() {
    return 0;
}

kompiliert nach

private final Object $lock = new Object[0];

public int foo() {
    synchronized($lock) {
        return 0;
    }
}
1
Michael

Ich möchte nur eine mögliche Lösung für eindeutige private Referenzen in atomaren Codeteilen ohne Abhängigkeiten erwähnen. Sie können eine statische Hashmap mit Sperren und eine einfache statische Methode namens atomic () verwenden, die die erforderlichen Referenzen automatisch anhand der Stapelinformationen (vollständiger Klassenname und Zeilennummer) erstellt. Dann können Sie diese Methode zum Synchronisieren von Anweisungen verwenden, ohne ein neues Sperrobjekt zu schreiben.

// Synchronization objects (locks)
private static HashMap<String, Object> locks = new HashMap<String, Object>();
// Simple method
private static Object atomic() {
    StackTraceElement [] stack = Thread.currentThread().getStackTrace(); // get execution point 
    StackTraceElement exepoint = stack[2];
    // creates unique key from class name and line number using execution point
    String key = String.format("%s#%d", exepoint.getClassName(), exepoint.getLineNumber()); 
    Object lock = locks.get(key); // use old or create new lock
    if (lock == null) {
        lock = new Object();
        locks.put(key, lock);
    }
    return lock; // return reference to lock
}
// Synchronized code
void dosomething1() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 1
        ...
    }
    // other command
}
// Synchronized code
void dosomething2() {
    // start commands
    synchronized (atomic()) {
        // atomic commands 2
        ...
    }
    // other command
}
0

Vermeiden Sie die Verwendung von synchronized(this) als Sperrmechanismus: Dies sperrt die gesamte Klasseninstanz und kann zu Deadlocks führen. In solchen Fällen müssen Sie den Code überarbeiten, um nur eine bestimmte Methode oder Variable zu sperren. Auf diese Weise wird die gesamte Klasse nicht gesperrt. Synchronised kann innerhalb der Methodenebene verwendet werden.
Anstatt synchronized(this) zu verwenden, zeigt der folgende Code, wie Sie eine Methode einfach sperren können.

   public void foo() {
if(operation = null) {
    synchronized(foo) { 
if (operation == null) {
 // enter your code that this method has to handle...
          }
        }
      }
    }
0
surendrapanday

Meine zwei Cent 2019, obwohl diese Frage schon geklärt sein könnte.

Das Sperren von "dies" ist nicht schlecht, wenn Sie wissen, was Sie tun, aber hinter den Kulissen ist das Sperren von "dies" (was leider das synchronisierte Schlüsselwort in der Methodendefinition zulässt).

Wenn Sie möchten, dass Benutzer Ihrer Klasse Ihre Sperre "stehlen" können (d. H. Verhindern, dass andere Threads damit umgehen), möchten Sie, dass alle synchronisierten Methoden warten, während eine andere Synchronisierungsmethode ausgeführt wird, und so weiter. Es sollte absichtlich und gut durchdacht sein (und daher dokumentiert werden, um Ihren Benutzern das Verständnis zu erleichtern).

Um genauer zu werden, müssen Sie im Gegenteil wissen, was Sie gewinnen (oder verlieren), wenn Sie ein nicht zugängliches Schloss schließen (niemand kann Ihr Schloss stehlen, Sie haben die totale Kontrolle usw.). ..).

Das Problem für mich ist, dass das synchronisierte Schlüsselwort in der Methodendefinitionssignatur es Programmierern einfach zu leicht macht nicht darüber nachzudenken worauf man sich einlässt, ist eine sehr wichtige Sache, über die man nachdenken sollte, wenn man nicht möchte in einem Multithread-Programm auf Probleme stoßen.

Man kann nicht behaupten, dass Sie nicht möchten, dass Benutzer Ihrer Klasse in der Lage sind, diese Dinge zu tun, oder dass Sie in der Regel ... Es hängt davon ab, welche Funktionalität Sie codieren. Sie können keine Daumenregel erstellen, da Sie nicht alle Anwendungsfälle vorhersagen können.

Betrachten Sie für z. Der Drucker, der eine interne Sperre verwendet, aber dann Probleme hat, diese von mehreren Threads aus zu verwenden, wenn die Ausgabe nicht verschachtelt werden soll.

Ob Ihre Sperre außerhalb der Klasse zugänglich ist oder nicht, entscheiden Sie als Programmierer anhand der Funktionalität der Klasse. Es ist Teil der API. Sie können zum Beispiel nicht von synchronisiert (this) zu synchronisiert (provateObjet) wechseln, ohne das Risiko einzugehen, dass Sie damit Änderungen im Code vornehmen.

Anmerkung 1: Ich weiß, dass Sie alles erreichen können, was synchronisiert (dies) "erreicht", indem Sie ein explizites Sperrobjekt verwenden und es verfügbar machen. Ich halte es jedoch für unnötig, wenn Ihr Verhalten gut dokumentiert ist und Sie tatsächlich wissen, was Sperren für "dies" bedeutet.

Anmerkung 2: Ich stimme nicht mit dem Argument überein, dass, wenn ein Code versehentlich Ihr Schloss stiehlt, es ein Fehler ist und Sie es lösen müssen. Dies ist in gewisser Weise das gleiche Argument wie die Aussage, dass ich alle meine Methoden veröffentlichen kann, auch wenn sie nicht öffentlich sein sollen. Wenn jemand "versehentlich" meine als privat bezeichnete Methode aufruft, ist dies ein Fehler. Warum diesen Unfall überhaupt erst ermöglichen !!! Wenn die Fähigkeit, Ihr Schloss zu stehlen, ein Problem für Ihre Klasse ist, lassen Sie es nicht zu. So einfach ist das.

0
Amit Mittal

Ein gutes Beispiel für die Verwendung synchronisiert (dies).

// add listener
public final synchronized void addListener(IListener l) {listeners.add(l);}
// remove listener
public final synchronized void removeListener(IListener l) {listeners.remove(l);}
// routine that raise events
public void run() {
   // some code here...
   Set ls;
   synchronized(this) {
      ls = listeners.clone();
   }
   for (IListener l : ls) { l.processEvent(event); }
   // some code here...
}

Wie Sie hier sehen können, verwenden wir Synchronize, um einfach mit einigen synchronisierten Methoden (möglicherweise Endlosschleifenmethode) zusammenzuarbeiten.

Natürlich kann es mit synchronized on private field sehr einfach umgeschrieben werden. Aber manchmal, wenn wir bereits ein Design mit synchronisierten Methoden haben (d. H. Eine ältere Klasse, von der wir abgeleitet sind, kann synchronisiert (dies) die einzige Lösung sein).

0
Bart Prokop

Es hängt von der Aufgabe ab, die Sie erledigen möchten, aber ich würde es nicht verwenden. Überprüfen Sie auch, ob die Thread-Speicherung, die Sie begleiten möchten, überhaupt nicht durch Synchronisieren (dies) erfolgen konnte. Es gibt auch einige nette Sperren in der API , die Ihnen helfen könnten :)

0
Harald Schilly