2013-08-12 7 views
8

Ich habe diese Art von Code sehr oft in Projekten gesehen, wo die Anwendung einen globalen Datenhalter haben möchte, also verwenden sie einen statischen Singleton, auf den jeder Thread zugreifen kann.Wie ist die Speichersichtbarkeit von Variablen, auf die in statischen Singletons in Java zugegriffen wird?

public class GlobalData { 

    // Data-related code. This could be anything; I've used a simple String. 
    // 
    private String someData; 
    public String getData() { return someData; } 
    public void setData(String data) { someData = data; } 

    // Singleton code 
    // 
    private static GlobalData INSTANCE; 
    private GlobalData() {} 
    public synchronized GlobalData getInstance() { 
     if (INSTANCE == null) INSTANCE = new GlobalData(); 
     return INSTANCE; 
    } 
} 

Ich hoffe, es ist einfach zu sehen, was vor sich geht. Man kann jederzeit auf jedem Thread GlobalData.getInstance().getData() anrufen. Wenn zwei Threads setData() mit unterschiedlichen Werten aufrufen, auch wenn Sie nicht garantieren können, welcher "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 Speicher Schranke passiert, wenn durch Synchronisierungen vorbei, flüchtige Variablen Zugriff usw.

das folgende Szenario in chronologischer Reihenfolge geschieht Stellen Sie sich vor:

// Thread 1 
GlobalData d = GlobalData.getInstance(); 
d.setData("one"); 

// Thread 2 
GlobalData d = GlobalData.getInstance(); 
d.setData("two"); 

// Thread 1 
String value = d.getData(); 

Ist es nicht möglich, dass der letzte Wert von value in Thread 1 Dose immer noch "one"? Der Grund dafür ist, dass Thread 2 niemals nach dem Aufruf von d.setData("two") irgendwelche synchronisierten 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 es synchronisiert ist.

+1

Sie sagen 'Man kann GlobalData.getInstance(). GetData() jederzeit auf jedem Thread aufrufen. Es ist threadsicher, also mache ich mir darüber keine Sorgen, aber es ist eigentlich nicht threadsicher, genau wegen des Szenarios, das du beschreibst. Bei der Threadsicherheit geht es nicht nur darum, ein Objekt in einem inkonsistenten Zustand zu sehen. Es geht auch darum, veraltete Daten zu sehen. – yair

+0

@yair Ja, aber ich habe wirklich versucht, den Aspekt der Speicher-Sichtbarkeit zu untersuchen, im Gegensatz zu inkonsistenten Zuständen, die auf der synchronen Ausführung basieren. –

Antwort

1

Ist es nicht möglich, dass der letzte Wert in Thread 1 noch "eins" sein kann?

Ja ist es. Das Java-Speichermodell basiert auf Beziehungen, die vor (hb) auftreten. In Ihrem Fall haben Sie nur getInstance Exit passiert-vor dem folgenden getInstance Eintrag, aufgrund der synchronisierten Schlüsselwort.

Also, wenn wir Ihr Beispiel nehmen (in dieser Reihenfolge den Faden Verschachtelung vorausgesetzt):

// Thread 1 
GlobalData d = GlobalData.getInstance(); //S1 
d.setData("one"); 

// Thread 2 
GlobalData d = GlobalData.getInstance(); //S2 
d.setData("two"); 

// Thread 1 
String value = d.getData(); 

Sie S1 hb S2 haben. Wenn Sie von Thread2 nach S2 d.getData() aufgerufen haben, würden Sie "eins" sehen. Aber das letzte Lesen von d ist nicht garantiert, "zwei" zu sehen.

2

Sie sind absolut richtig.

Es gibt keine Garantie, dass Schreibvorgänge in einem Thread sichtbar ist, ist ein anderer.

diese Garantie Um Ihnen die volatile Schlüsselwort verwenden müssten:

private volatile String someData; 

Im Übrigen können Sie die Java-Classloader-Provider Thread-sicher faul nutzen init Ihrer Singleton here dokumentiert. Dies vermeidet das Schlüsselwort synchronized und erspart Ihnen daher einige Sperren.

Es ist erwähnenswert, dass die derzeit akzeptierte Best Practice die Verwendung eines enum zum Speichern von Singleton-Daten in Java ist.

2

Korrekt, es ist möglich, dass Thread 1 immer noch den Wert "eins" sieht, da kein Speichersynchronisationsereignis aufgetreten ist und keine Ereignisse vor der Beziehung zwischen Thread 1 und 2 auftreten (siehe Abschnitt 17.4.5 of the JLS).

Wenn someData war volatile dann Thread 1 würde den Wert als "zwei" sehen (unter der Annahme, Thread 2 abgeschlossen, bevor Thread 1 den Wert abgerufen).

Schließlich, und off Thema, ist die Implementierung des Singleton etwas weniger als ideal, da es bei jedem Zugriff synchronisiert. Es ist im Allgemeinen besser, eine Enumeration zu verwenden, um ein Singleton zu implementieren, oder zumindest die Instanz in einem statischen Initialisierer zuzuordnen, sodass kein Aufruf des Konstruktors in der Methode getInstance erforderlich ist.

Verwandte Themen