Immer wenn auf SO eine Frage zur Java-Synchronisation auftaucht, möchten einige Leute darauf hinweisen, dass synchronized(this)
dies vermieden werden sollte. Stattdessen sei eine Sperre einer privaten Referenz vorzuziehen.
Einige der angegebenen Gründe sind:
- Ein böser Code kann Ihr Schloss stehlen (sehr beliebt, hat auch eine "versehentliche" Variante)
- Alle synchronisierten Methoden innerhalb derselben Klasse verwenden genau dieselbe Sperre, wodurch der Durchsatz verringert wird
- Sie legen (unnötigerweise) zu viele Informationen offen
Andere Leute, einschließlich mir, argumentieren, dass synchronized(this)
eine Redewendung, die häufig verwendet wird (auch in Java-Bibliotheken), sicher und gut verstanden ist. Es sollte nicht vermieden werden, da Sie einen Fehler haben und keine Ahnung haben, was in Ihrem Multithread-Programm vor sich geht. Mit anderen Worten: Wenn es anwendbar ist, dann benutze es.
Ich bin daran interessiert, einige Beispiele aus der Praxis zu sehen (kein Foobar-Zeug), bei denen this
es vorzuziehen ist , eine Sperre zu vermeiden, wenn synchronized(this)
dies auch der Fall ist.
Deshalb: sollten Sie es immer vermeiden synchronized(this)
und durch eine Sperre 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 vonsynchronized(this)
berücksichtigt - Wenn Sie Bloch oder andere Autoritäten zu diesem Thema zitieren, lassen Sie die Teile, die Sie nicht mögen, nicht aus (z. B. Effektives Java, Punkt zur Thread-Sicherheit: In der Regel ist dies die Sperre für die Instanz selbst, es gibt jedoch Ausnahmen.)
- Wenn Sie eine andere Granularität als die bereitgestellte Sperre benötigen
synchronized(this)
,synchronized(this)
ist diese nicht anwendbar, sodass dies nicht das Problem ist
Antworten:
Ich werde jeden Punkt separat behandeln.
Ich mache mir aus Versehen mehr Sorgen . Es kommt darauf an, dass diese Verwendung
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 wieCollections.synchronizedMap
(siehe Javadoc).Das ist zu simpel gedacht; Nur loszuwerden
synchronized(this)
wird das Problem nicht lösen. Die richtige Synchronisation für den Durchsatz erfordert mehr Überlegungen.Dies ist eine Variante von # 1. Die Verwendung von
synchronized(this)
ist Teil Ihrer Benutzeroberfläche. Wenn Sie dies nicht ausgesetzt haben wollen / müssen, tun Sie es nicht.quelle
keySet()
undvalues()
sich nicht auf (ihre)this
, sondern auf die Karteninstanz beschränken, was wichtig ist, um ein konsistentes Verhalten für alle Kartenoperationen zu erzielen. Der Grund dafür, dass das Sperrobjekt in eine Variable zerlegt wird, besteht darin, dass die UnterklasseSynchronizedSortedMap
es zum Implementieren von Unterkarten benötigt , die die ursprüngliche Karteninstanz sperren.Nun, zunächst sollte darauf hingewiesen werden, dass:
ist semantisch äquivalent zu:
Das ist ein Grund, nicht zu verwenden
synchronized(this)
. Sie könnten argumentieren, dass Sie Dinge rund um densynchronized(this)
Block tun können . Der übliche Grund besteht darin, zu vermeiden, dass die synchronisierte Prüfung überhaupt durchgeführt werden muss, was zu allen möglichen Parallelitätsproblemen führt, insbesondere zu dem Problem der doppelten verriegelten Verriegelung , das nur zeigt, wie schwierig es sein kann, eine relativ einfache Prüfung durchzuführen threadsicher.Ein privates Schloss ist ein Abwehrmechanismus, was niemals eine schlechte Idee ist.
Wie Sie bereits angedeutet haben, können private Sperren die Granularität steuern. Ein Satz von Operationen an einem Objekt kann völlig unabhängig von einem anderen sein,
synchronized(this)
schließt jedoch den Zugriff auf alle gegenseitig aus.synchronized(this)
gibt dir einfach wirklich nichts.quelle
Während Sie synchronized (this) verwenden, verwenden Sie die Klasseninstanz als Sperre. Dies bedeutet, dass der Thread 2 warten sollte , während die Sperre von Thread 1 übernommen wird .
Angenommen, der folgende Code:
Bei Methode 1, bei der die Variable a geändert wird, und bei Methode 2, bei der die Variable b geändert wird , sollte die gleichzeitige Änderung derselben Variablen durch zwei Threads vermieden werden. Aber während thread1 Modifizieren a und Thread2 modifizierende b kann ohne Race - Bedingung durchgeführt werden.
Leider lässt der obige Code dies nicht zu, da wir dieselbe Referenz für eine Sperre verwenden. Dies bedeutet, dass Threads warten sollten, auch wenn sie sich nicht in einem Race-Zustand befinden, und offensichtlich opfert der Code die Parallelität des Programms.
Die Lösung besteht darin, zwei verschiedene Sperren für zwei verschiedene Variablen zu verwenden:
Das obige Beispiel verwendet feinkörnigere Sperren (2 Sperren statt einer ( lockA und lockB für die Variablen a bzw. b )) und ermöglicht dadurch eine bessere Parallelität. Andererseits wurde es komplexer als das erste Beispiel ...
quelle
Obwohl ich damit einverstanden bin, dass ich mich nicht blind an dogmatische Regeln halte, erscheint Ihnen das Szenario "Schloss stehlen" so exzentrisch? 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ösartigen Code glauben, denken Sie daran, dass dieser Code von Dritten stammen kann (z. B. wenn Sie einen Anwendungsserver entwickeln).
Die "zufällige" Version scheint weniger wahrscheinlich, aber wie sie sagen, "machen Sie etwas idiotensicher und jemand wird einen besseren Idioten erfinden".
Also stimme ich der Denkschule zu, die davon abhängt, was die Klasse macht.
Bearbeiten Sie die ersten 3 Kommentare von eljenso:
Ich habe das Problem des Schlossdiebstahls noch nie erlebt, aber hier ist ein imaginäres Szenario:
Angenommen, Ihr System ist ein Servlet-Container, und das Objekt, das wir in Betracht ziehen, ist die
ServletContext
Implementierung. DiegetAttribute
Methode muss threadsicher sein, da Kontextattribute gemeinsam genutzte Daten sind. so deklarieren Sie es alssynchronized
. Stellen wir uns auch vor, dass Sie einen öffentlichen Hosting-Service basierend auf Ihrer Container-Implementierung bereitstellen.Ich bin Ihr Kunde und setze mein "gutes" Servlet auf Ihrer Site ein. Es kommt vor, dass mein Code einen Aufruf an enthält
getAttribute
.Ein als anderer Kunde getarnter Hacker stellt sein bösartiges Servlet auf Ihrer Website bereit. Es enthält den folgenden Code in der
init
Methode:Angenommen, wir verwenden denselben Servlet-Kontext (gemäß der Spezifikation zulässig, solange sich die beiden Servlets auf demselben virtuellen Host befinden), ist mein Anruf für
getAttribute
immer gesperrt. Der Hacker hat ein DoS für mein Servlet erreicht.Dieser Angriff ist nicht möglich, wenn
getAttribute
auf einer privaten Sperre synchronisiert ist, da Code von Drittanbietern diese Sperre nicht erhalten kann.Ich gebe zu, dass das Beispiel erfunden ist und eine vereinfachte Ansicht darüber, wie ein Servlet-Container funktioniert, aber meiner Meinung nach beweist es den Punkt.
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, wenn ein Thread eine Instanz auf unbestimmte Zeit sperrt?
quelle
Es hängt von der Situation ab.
Wenn es nur eine oder mehrere Freigabeeinheiten gibt.
Das vollständige Arbeitsbeispiel finden Sie hier
Eine kleine Einführung.
Threads und gemeinsam nutzbare Entitäten
Es ist möglich, dass mehrere Threads auf dieselbe Entität zugreifen, z. B. mehrere connectionThreads, die eine einzige messageQueue gemeinsam nutzen. Da die Threads gleichzeitig ausgeführt werden, besteht möglicherweise die Möglichkeit, dass Daten von anderen überschrieben werden, was zu einer durcheinandergebrachten Situation führen kann.
Wir brauchen also eine Möglichkeit, um sicherzustellen, dass jeweils nur ein Thread auf eine gemeinsam nutzbare Entität zugreift. (KONKURRENZ).
Synchronisierter Block Der
synchronisierte () Block ist eine Möglichkeit, den gleichzeitigen Zugriff auf gemeinsam nutzbare Entitäten sicherzustellen.
Zunächst eine kleine Analogie
Angenommen, es gibt zwei Personen P1, P2 (Gewinde), ein Waschbecken (gemeinsam nutzbare Einheit) in einem Waschraum und eine Tür (Schloss).
Jetzt möchten wir, dass jeweils eine Person das Waschbecken benutzt.
Ein Ansatz besteht darin, die Tür mit 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 das Waschbecken benutzen.
Syntax.
"this" stellte die der Klasse zugeordnete intrinsische Sperre bereit (Java-Entwickler hat die Objektklasse so entworfen, dass jedes Objekt als Monitor fungieren kann). Der obige Ansatz funktioniert einwandfrei, wenn nur eine gemeinsam genutzte Entität und mehrere Threads (1: N) vorhanden sind. N gemeinsam nutzbare Einheiten - M Fäden 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 jeweils nur p1 ein Waschbecken verwenden, während p2 draußen wartet. Es ist eine Verschwendung von Ressourcen, da niemand B2 (Waschbecken) verwendet. 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.
Weitere Informationen zu Threads ----> finden Sie hier
quelle
In den C # - und Java-Lagern scheint diesbezüglich ein anderer Konsens zu bestehen. Der Großteil des Java-Codes, den ich gesehen habe, verwendet:
Während sich der Großteil des C # -Codes für das wohl sicherere entscheidet:
Die C # -Sprache ist sicherlich sicherer. Wie bereits erwähnt, kann von außerhalb der Instanz kein böswilliger / versehentlicher Zugriff auf die Sperre erfolgen. Java-Code birgt ebenfalls dieses Risiko, aber es scheint, dass sich die Java-Community im Laufe der Zeit auf die etwas weniger sichere, aber etwas knappere Version konzentriert hat.
Das ist nicht als Ausgrabung gegen Java gedacht, sondern nur ein Spiegelbild meiner Erfahrung mit beiden Sprachen.
quelle
Das
java.util.concurrent
Paket hat die Komplexität meines thread-sicheren Codes erheblich reduziert. Ich habe nur anekdotische Beweise, aber die meisten Arbeiten, mit denen ich gesehen habe,synchronized(x)
scheinen darin zu bestehen, ein Schloss, ein Semaphor oder einen Latch erneut zu implementieren, aber die Monitore der unteren Ebene zu verwenden.In diesem Sinne ist die Synchronisierung mit einem dieser Mechanismen analog zur Synchronisierung auf einem internen Objekt, anstatt eine Sperre zu verlieren. Dies ist insofern von Vorteil, als Sie absolut sicher sind, dass Sie den Eintritt in den Monitor durch zwei oder mehr Threads steuern.
quelle
final
Variablen)Lock
API].Zu verwendender Beispielcode,
ReentrantLock
der dieLock
Schnittstelle implementiertVorteile der Sperre gegenüber synchronisiert (dies)
Die Verwendung synchronisierter Methoden oder Anweisungen erzwingt, dass alle Sperrenerfassungen und -freigaben blockstrukturiert erfolgen.
Lock-Implementierungen bieten zusätzliche Funktionen gegenüber der Verwendung synchronisierter Methoden und Anweisungen
tryLock()
)lockInterruptibly()
)tryLock(long, TimeUnit)
).Eine Lock-Klasse kann auch ein Verhalten und eine Semantik bereitstellen, die sich erheblich von denen der impliziten Monitorsperre unterscheiden, z
Schauen Sie sich diese SE-Frage zu verschiedenen Arten von
Locks
:Synchronisation gegen Sperre
Sie können die Thread-Sicherheit erreichen, indem Sie anstelle der synchronisierten Blöcke die erweiterte Parallelitäts-API verwenden. Diese Dokumentation Seite bietet eine gute Programmierkonstrukte Thread - Sicherheit zu erreichen.
Sperrobjekte unterstützen Sperrsprachen, die viele gleichzeitige Anwendungen vereinfachen.
Ausführende definieren eine allgemeine API zum Starten und Verwalten von Threads. Von java.util.concurrent bereitgestellte Executor-Implementierungen bieten eine Thread-Pool-Verwaltung, die für umfangreiche Anwendungen geeignet ist.
Gleichzeitige Sammlungen erleichtern die Verwaltung großer Datensammlungen und können den Synchronisierungsbedarf erheblich reduzieren.
Atomic Variables verfügen über Funktionen, die die Synchronisation minimieren und Speicherkonsistenzfehler vermeiden.
ThreadLocalRandom (in JDK 7) bietet eine effiziente Generierung von Pseudozufallszahlen aus mehreren Threads.
Weitere Programmierkonstrukte finden Sie auch in den Paketen java.util.concurrent und java.util.concurrent.atomic .
quelle
Wenn Sie das entschieden haben:
dann sehe ich kein tabu über synchronizezd (dies).
Einige Leute verwenden absichtlich synchronisiert (dies) (anstatt die Methode synchronisiert zu markieren) innerhalb des gesamten Inhalts einer Methode, weil sie denken, dass es für den Leser "klarer" ist, auf welchem Objekt tatsächlich synchronisiert wird. Solange die Leute eine informierte Entscheidung treffen (z. B. verstehen, dass sie dadurch tatsächlich zusätzliche Bytecodes in die Methode einfügen und dies sich auf mögliche Optimierungen auswirken könnte), sehe ich kein besonderes Problem damit . Sie sollten immer das gleichzeitige Verhalten Ihres Programms dokumentieren, daher sehe ich das Argument "'synchronisiert' veröffentlicht das Verhalten" nicht als so überzeugend an.
In Bezug auf die Frage, welche Objektsperre Sie verwenden sollten, ist es meines Erachtens nichts Falsches, das aktuelle Objekt zu synchronisieren, wenn dies von der Logik Ihrer Arbeit und der typischen Verwendung Ihrer Klasse erwartet wird . Bei einer Sammlung ist das Objekt, von dem Sie logischerweise erwarten würden, dass es gesperrt wird, im Allgemeinen die Sammlung selbst.
quelle
Ich denke, es gibt eine gute Erklärung dafür, warum jede dieser Techniken in einem Buch mit dem Titel Java Concurrency In Practice von Brian Goetz eine wichtige Rolle spielt. Er macht einen Punkt sehr deutlich - Sie müssen das gleiche Schloss "ÜBERALL" verwenden, um den Zustand Ihres Objekts zu schützen. Die synchronisierte Methode und die Synchronisierung eines Objekts gehen häufig Hand in Hand. ZB synchronisiert Vector alle seine Methoden. Wenn Sie ein Handle für ein Vektorobjekt haben und "setzen, wenn nicht vorhanden" ausführen, schützt Sie die bloße Vektorsynchronisierung der einzelnen Methoden nicht vor einer Beschädigung des Status. Sie müssen mit synchronized (vectorHandle) synchronisieren. Dies führt dazu, dass die gleiche Sperre von jedem Thread erfasst wird, der ein Handle für den Vektor hat, und schützt den Gesamtzustand des Vektors. Dies wird als clientseitige Sperre bezeichnet. Wir wissen, dass der Vektor tatsächlich alle seine Methoden synchronisiert (dies) / synchronisiert, und daher führt die Synchronisierung auf dem Objekt vectorHandle zu einer ordnungsgemäßen Synchronisation des Zustands der Vektorobjekte. Es ist dumm 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.
Zusammenfassend
quelle
Nein, das solltest du nicht immer . Ich neige jedoch dazu, dies zu vermeiden, wenn es bei einem bestimmten Objekt mehrere Bedenken gibt, die nur in Bezug auf sich selbst threadsicher sein müssen. Beispielsweise haben Sie möglicherweise ein veränderbares Datenobjekt mit den Feldern "label" und "parent". Diese müssen threadsicher sein, aber das Ändern des einen muss nicht verhindern, dass der andere geschrieben / gelesen wird. (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 auslöst, anstatt genau zu überlegen, wie Threads möglicherweise umeinander arbeiten dürfen. Das Verwenden
synchronized(this)
ist noch ungeschickter und unsozialer, da es heißt: "Niemand darf etwas ändern an dieser Klasse während ich das Schloss halte". Wie oft müssen Sie das tatsächlich tun?Ich hätte viel lieber körnigere Schlösser; Selbst wenn Sie verhindern möchten, dass sich alles ändert (vielleicht serialisieren Sie das Objekt), können Sie einfach alle Sperren erwerben, um dasselbe zu erreichen. Außerdem ist dies auf diese Weise expliziter. Bei der Verwendung
synchronized(this)
ist nicht klar, warum Sie synchronisieren oder welche Nebenwirkungen auftreten können. Wenn Siesynchronized(labelMonitor)
oder noch besser verwendenlabelLock.getWriteLock().lock()
, ist klar, was Sie tun und auf welche Auswirkungen Ihr kritischer Abschnitt beschränkt ist.quelle
Kurze Antwort : Sie müssen den Unterschied verstehen und je nach Code eine Auswahl treffen.
Lange Antwort : Im Allgemeinen würde ich lieber versuchen, eine Synchronisierung (dies) zu vermeiden , um Konflikte zu reduzieren, aber private Sperren erhöhen die Komplexität, die Sie beachten müssen. Verwenden Sie also die richtige Synchronisation für den richtigen Job. Wenn Sie nicht so viel Erfahrung mit Multithread-Programmierung haben, würde ich mich lieber an die Instanzsperre halten und mich über dieses Thema informieren. (Das heißt: Nur die Verwendung von synchronize (this) macht Ihre Klasse nicht automatisch vollständig threadsicher.) Dies ist kein einfaches Thema, aber sobald Sie sich daran gewöhnt haben, ist die Antwort, ob Sie synchronize (this) verwenden oder nicht, selbstverständlich .
quelle
Eine Sperre wird entweder zur Sichtbarkeit oder zum Schutz einiger Daten vor gleichzeitigen Änderungen verwendet, die zu Rennen führen können.
Wenn Sie nur primitive Operationen ausführen müssen, um atomar zu sein, stehen Optionen wie
AtomicInteger
und dergleichen zur Verfügung.Angenommen, Sie haben zwei Ganzzahlen, die wie
x
undy
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 sollte nur den Zustand schützen, der miteinander in Beziehung steht. Nicht weniger und nicht mehr. Wenn Sie
synchronized(this)
in jeder Methode verwenden, werden alle Threads selbst dann in Konflikt geraten, wenn der Status der Klasse nicht in Beziehung steht, selbst wenn der Status in Beziehung gesetzt wird.Im obigen Beispiel habe ich nur ein Verfahren , das sowohl mutiert
x
undy
nicht zwei verschiedene Verfahren , wiex
undy
miteinander verknüpft , und , wenn ich zwei verschiedene Verfahren zum Mutieren gegeben hattex
undy
separat dann wäre es nicht gewesen threadsicher sein.Dieses Beispiel soll nur demonstrieren und nicht unbedingt, wie es implementiert werden sollte. Der beste Weg, dies zu tun, wäre, es IMMUTABLE zu machen .
Im Gegensatz zum
Point
Beispiel gibt es ein BeispielTwoCounters
von @Andreas, bei dem der Status, der durch zwei verschiedene Sperren geschützt wird, da der Status nicht miteinander in Beziehung steht.Der Prozess der Verwendung verschiedener Sperren zum Schutz nicht verwandter Zustände wird als Lock Striping oder Lock Splitting bezeichnet
quelle
Der Grund , nicht zu synchronisieren auf das ist , dass manchmal braucht man mehr als ein Schloss (das zweite Schloss oft nach einigen zusätzlichen Denken entfernt wird, aber Sie es noch brauchen im Zwischenzustand). Wenn Sie sperren diese , müssen Sie immer daran denken , welche der beiden Schlösser ist dies ; Wenn Sie ein privates Objekt sperren, sagt Ihnen der Variablenname dies.
Aus Sicht des Lesers müssen Sie immer die beiden Fragen beantworten , wenn Sie feststellen, dass dies blockiert ist:
Ein Beispiel:
Wenn zwei Threads
longOperation()
in zwei verschiedenen Instanzen von beginnenBadObject
, erhalten sie ihre Sperren. Wenn es Zeit zum Aufrufen istl.onMyEvent(...)
, haben wir einen Deadlock, da keiner der Threads die Sperre des anderen Objekts erhalten 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.
quelle
BadObject
AlongOperation
B aufruft , A's übergibtmyListener
und umgekehrt. Nicht unmöglich, aber ziemlich verworren, was meine früheren Punkte unterstützt.Wie hier bereits gesagt, kann der synchronisierte Block eine benutzerdefinierte Variable als Sperrobjekt verwenden, wenn die synchronisierte Funktion nur "dies" verwendet. Und natürlich können Sie mit Bereichen Ihrer Funktion manipulieren, die synchronisiert werden sollen und so weiter.
Aber jeder sagt, dass kein Unterschied zwischen synchronisierter Funktion und Block, der die gesamte Funktion abdeckt, "dies" als Sperrobjekt verwendet. Das ist nicht wahr, der Unterschied liegt im Bytecode, der in beiden Situationen generiert wird. Im Falle einer synchronisierten Blocknutzung sollte eine lokale Variable zugewiesen werden, die auf "this" verweist. Infolgedessen 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 eines synchronisierten Blocks ist aus folgenden Gründen nicht gut:
Für weitere Informationen in diesem Bereich würde ich empfehlen, diesen Artikel zu lesen: http://java.dzone.com/articles/synchronized-considered
quelle
Dies ist eigentlich nur eine Ergänzung zu den anderen Antworten. Wenn Ihr Hauptargument gegen die Verwendung privater Objekte zum Sperren darin besteht, dass Ihre Klasse mit Feldern überfüllt ist, die nicht mit der Geschäftslogik zusammenhängen, muss Project Lombok
@Synchronized
das Boilerplate zur Kompilierungszeit generieren:kompiliert zu
quelle
Ein gutes Beispiel für die Verwendung synchronisiert (dies).
Wie Sie hier sehen können, verwenden wir die Synchronisierung, um auf einfache Weise (möglicherweise Endlosschleife der Ausführungsmethode) mit einigen synchronisierten Methoden dort zusammenzuarbeiten.
Natürlich kann es sehr einfach mit synchronisiertem privatem Feld umgeschrieben werden. Aber manchmal, wenn wir bereits ein Design mit synchronisierten Methoden haben (dh Legacy-Klasse, von der wir abgeleitet sind, kann synchronisiert (dies) die einzige Lösung sein).
quelle
this
. Es könnte ein privates Feld sein.Es hängt von der Aufgabe ab, die Sie erledigen möchten, aber ich würde sie nicht verwenden. Überprüfen Sie auch, ob die Thread-Speicherung, die Sie begleiten möchten, überhaupt nicht durch Synchronisieren (dieses) erfolgen kann. Es gibt auch einige nette Sperren in der API , die Ihnen helfen könnten :)
quelle
Ich möchte nur eine mögliche Lösung für eindeutige private Referenzen in atomaren Teilen des Codes ohne Abhängigkeiten erwähnen. Sie können eine statische Hashmap mit Sperren und eine einfache statische Methode namens atomic () verwenden, mit der erforderliche Referenzen automatisch mithilfe von Stapelinformationen (vollständiger Klassenname und Zeilennummer) erstellt werden. Anschließend können Sie diese Methode zum Synchronisieren von Anweisungen verwenden, ohne ein neues Sperrobjekt zu schreiben.
quelle
Vermeiden Sie die Verwendung
synchronized(this)
als Sperrmechanismus: Dies sperrt die gesamte Klasseninstanz und kann Deadlocks verursachen. Refaktorieren Sie in solchen Fällen den Code so, dass nur eine bestimmte Methode oder Variable gesperrt wird. Auf diese Weise wird die gesamte Klasse nicht gesperrt.Synchronised
kann innerhalb der Methodenebene verwendet werden.Anstatt zu verwenden
synchronized(this)
, zeigt der folgende Code, wie Sie eine Methode einfach sperren können.quelle
Meine zwei Cent im Jahr 2019, obwohl diese Frage bereits geklärt sein könnte.
Das Sperren von 'this' ist nicht schlecht, wenn Sie wissen, was Sie tun, aber hinter den Kulissen ist das Sperren von 'this' (was leider das synchronisierte Schlüsselwort in der Methodendefinition erlaubt).
Wenn Sie tatsächlich möchten, dass Benutzer Ihrer Klasse Ihre Sperre "stehlen" können (dh 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, damit Ihre Benutzer es besser verstehen).
Um dies näher zu erläutern, müssen Sie umgekehrt wissen, was Sie "gewinnen" (oder "verlieren"), wenn Sie ein nicht zugängliches Schloss sperren (niemand kann Ihr Schloss "stehlen", Sie haben die totale Kontrolle und so weiter. ..).
Das Problem für mich ist, dass das synchronisierte Schlüsselwort in der Signatur der Methodendefinition es Programmierern einfach zu einfach macht, nicht darüber nachzudenken, was sie sperren sollen. Dies ist eine sehr wichtige Sache, über die man nachdenken muss, wenn man in einem Multi nicht auf Probleme stoßen möchte -threaded Programm.
Man kann nicht behaupten, dass "normalerweise" Sie nicht möchten, dass Benutzer Ihrer Klasse diese Dinge tun können, oder dass "normalerweise" Sie möchten ... 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 zum Beispiel den Printwriter, der eine interne Sperre verwendet, aber die Leute Schwierigkeiten haben, diese von mehreren Threads aus zu verwenden, wenn sie nicht möchten, dass ihre Ausgabe verschachtelt wird.
Ob Ihre Sperre außerhalb der Klasse zugänglich ist oder nicht, liegt Ihre Entscheidung als Programmierer auf der Grundlage der Funktionalität der Klasse. Es ist Teil der API. Sie können beispielsweise nicht von synchronisiert (dies) zu synchronisiert (provateObjet) wechseln, ohne das Risiko einzugehen, dass Änderungen im Code, der ihn verwendet, beschädigt werden.
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 denke jedoch, dass es unnötig ist, wenn Ihr Verhalten gut dokumentiert ist und Sie tatsächlich wissen, was das Sperren von "dies" bedeutet.
Anmerkung 2: Ich stimme dem Argument nicht zu, dass ein Code, der versehentlich Ihr Schloss stiehlt, ein Fehler ist und Sie ihn lösen müssen. Dies ist in gewisser Weise das gleiche Argument wie die Aussage, dass ich alle meine Methoden öffentlich machen kann, auch wenn sie nicht öffentlich sein sollen. Wenn jemand "versehentlich" meine beabsichtigte private Methode aufruft, ist dies ein Fehler. Warum diesen Unfall überhaupt aktivieren !!! Wenn die Fähigkeit, Ihr Schloss zu stehlen, ein Problem für Ihre Klasse ist, lassen Sie es nicht zu. So einfach ist das.
quelle
Ich denke, Punkte eins (jemand anderes, der Ihre Sperre verwendet) und zwei (alle Methoden, die dieselbe Sperre unnötig verwenden) können in jeder ziemlich großen Anwendung auftreten. Besonders wenn es keine gute Kommunikation zwischen Entwicklern gibt.
Es ist nicht in Stein gemeißelt, es geht hauptsächlich um bewährte Verfahren und die Vermeidung von Fehlern.
quelle