Ich habe diese Art von Code oft in Projekten gesehen, wo die Anwendung einen globalen Datenbehälter benötigt, also verwenden sie einen statischen Singleton, auf den jeder Thread zugreifen kann.
%Vor% Ich hoffe, es ist leicht zu sehen, was vor sich geht. Man kann GlobalData.getInstance().getData()
jederzeit auf jedem Thread aufrufen. Wenn zwei Threads setData () mit anderen Werten aufrufen, auch wenn Sie nicht garantieren können, welche "gewinnt", mache ich mir darüber keine Sorgen.
Aber Thread-Sicherheit ist hier nicht mein Anliegen. Worüber ich besorgt bin, ist die Sichtbarkeit des Speichers. Wenn in Java eine Speicherbarriere vorhanden ist, wird der zwischengespeicherte Speicher zwischen den entsprechenden Threads synchronisiert. Eine Speicherbarriere tritt auf, wenn Synchronisationen durchlaufen werden, auf flüchtige Variablen zugegriffen wird.
Stellen Sie sich folgendes Szenario in chronologischer Reihenfolge vor:
%Vor% Ist es nicht möglich, dass der letzte Wert von value
in Thread 1 immer noch "one"
ist? Der Grund dafür war, dass Thread 2 nach dem Aufruf von d.setData("two")
niemals synchronisierte Methoden aufgerufen hat, so dass es nie eine Speicherbarriere gab? Beachten Sie, dass die Speicherbarriere in diesem Fall jedes Mal auftritt, wenn getInstance()
aufgerufen wird, weil sie synchronisiert ist.
Ist es nicht möglich, dass der letzte Wert in Thread 1 immer noch "eins" sein kann?
Ja ist es. Das Java-Speichermodell basiert auf Beziehungen, die vor (hb) auftreten. In Ihrem Fall haben Sie aufgrund des synchronisierten Schlüsselworts nur getInstance
exit happens-vor dem folgenden getInstance
entry .
Wenn wir also Ihr Beispiel nehmen (vorausgesetzt, das Thread-Interleaving ist in dieser Reihenfolge):
%Vor% Sie haben S1 hb S2. Wenn Sie d.getData()
von Thread2 nach S2 aufgerufen haben, würden Sie "eins" sehen. Aber das letzte Lesen von d ist nicht garantiert, um "zwei" zu sehen.
Sie sind absolut richtig.
Es gibt keine Garantie, dass Schreibvorgänge in einem Thread
sichtbar sind, ist ein anderer.
Um diese Garantie zu bieten, müssen Sie das Schlüsselwort volatile
verwenden:
Übrigens können Sie den Java-Klassenlader nutzen, um thread-sichere Lazy-Init Ihres Singleton als dokumentierte hier bereitzustellen. Dies vermeidet das Schlüsselwort synchronized
und erspart Ihnen daher einige Sperren.
Es ist erwähnenswert, dass die derzeit akzeptierte Best Practice darin besteht, ein enum
zum Speichern von Singleton-Daten in Java zu verwenden.
Korrekt, es ist möglich, dass Thread 1 den Wert immer noch als "Eins" ansieht, da kein Speichersynchronisationsereignis aufgetreten ist und keine Ereignisse vor der Beziehung zwischen Thread 1 und Thread 2 auftreten (siehe Abschnitt 17.4.5 der JLS ).
Wenn someData
volatile
war, dann würde Thread 1 den Wert als "zwei" sehen (angenommen, Thread 2 wurde abgeschlossen, bevor Thread 1 den Wert abgerufen hat).
Zu guter Letzt ist die Implementierung des Singleton etwas weniger als ideal, da sie bei jedem Zugriff synchronisiert wird. Es ist im Allgemeinen besser, eine Enumeration zu verwenden, um ein Singleton zu implementieren, oder zumindest die Instanz in einem statischen Initialisierer zuzuweisen, sodass kein Aufruf des Konstruktors in der getInstance
-Methode erforderlich ist.
Tags und Links java java-memory-model