Keyword volatile

Status
Nicht offen für weitere Antworten.

hdi

Top Contributor
Hey,

habe gerade in einem Design-Pattern Buch (Design Pattern von Kopf bis Fuss / Head First Design Pattern)
etwas über's Singleton gelesen. Das meiste verstehe ich:

[HIGHLIGHT="Java"]public class Singleton {

private volatile static Singleton instance;

private Singleton() {
}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}[/HIGHLIGHT]

Also, wieso die getInstance() so ist, wie sie ist, ist mir schon klar:
Es könnte ja passieren dass 2 Threads gleichzeitig getInstance() aufrufen,
wenn die Instanz noch nicht exisitert. Und dann hat man trotz "Singleton" 2 Objekte.

Deswegen macht diese doppelte null-Prüfung auch Sinn, und das ganze
ist nur deshalb so gemacht, weil Synchronisation teuer ist und wir es nur machen
wenn das Objekt instantiiert werden muss, ansonsten nicht mehr.

Soweit sogut, aber dieses "volatile" verstehe ich nicht so richtig.
Wieso ist das nötig? Ich weiss dass es irgendwas mit Threading/Locking zu tun hat,
aber wenn ich mir die getInstance() ansehe, verstehe ich nicht wie es da noch zu einem Problem kommen kann.

Was bedeutet volatile genau, bzw. welches ungewollte Ereignis könnte eintreten,
wenn ich das weglasse?

Danke
 

fjord

Bekanntes Mitglied
Es kann dir passieren, dass die Änderung einer Variable in einem Thread nicht für andere Threads sichtbar ist. Der Grund dafür ist, dass du mehrere Prozessoren haben kannst. Jeder der Prozessoren hat einen eigenen Cache und und damit eine lokale Version der Variablen. Die Threads sind nicht verpflichtet diese Änderung mitzuteilen, weil dafür extra Aufwand betrieben werden muss. (Bin mir gerade nicht sicher, ob volatile bei Rechnern mit einem Prozessor nötig wäre, aber man sollte ja eh nicht für eine bestimmte Hardware entwickeln.)
Du als Programmierer hast mit volatile die Möglichkeit Variablen zu kennzeichnen von denen du weißt, dass sie von mehreren Threads benutzt werden. Das bewirkt, dass die Änderung über den lokalen Cache hinaus weitergegeben wird und alle anderen Threads den neuen Wert der Variablen sehen.

In deinem Beispiel könnte es also ohne volatile passieren, dass Thread 1 feststellt, dass instance null ist, ein Singleton erstellt und instance zuweißt, diese Änderung von instance aber nur ihm bekannt ist. Ein 2. Thread sieht instance immernoch als null, erstellt ein weiteres Singleton und weißt es auch instance zu.
 

Noctarius

Top Contributor
Ein schönes Buch zu dem ganzen Kram "Java Concurrency In Practice" oder der Vortrag über das Java Memory Modell auf der JAX im April :)

Btw tauchen da noch mehr Leute von Euch auf? =)
 
M

maki

Gast
Aber was eMule/aMule ist wissen auch bzw. vor allem Studenten, oder? ;)
 

ice-breaker

Top Contributor
oder der Vortrag über das Java Memory Modell auf der JAX im April :)

JAX TV: Java-Programmierung im Multicore-Zeitalter
ich vermute mal es wird der gleiche Beitag sein :D

Wird nicht aber in dem Beitrag sogar erwähnt, dass synchronized garantiert, dass die Variable in allen Threads auf die korrekte Instanz zugreift und dass das volatile nur Sinn machen würde bei einer Instanzierung ohne synchronized? Denn wenn ich das recht in Erinnerung haben, ist das synchronized quasi (jetzt nur auf das Problem betrachtet) ein volatile für alle Member-Variablen dieser Klasse.
 

fjord

Bekanntes Mitglied
Wenn ich mich richtig erinnere (finde leider gerade keine Quelle dazu) dann ist dies nur der Fall, wenn alle Zugriffe auf die Variable innerhalb eines synchronized Blocks stattfinden. Der erste Zugriff instance == null ist aber ja außerhalb des synchronized Blocks, könnte also noch den alten Wert zurückliefern.

Ich hab hier höchstens ein ähnliches Konstrukt von Brian Goetz gefunden. Wenn volatile da überflüssig wäre, dann müsste er das ja wissen. ;)
Listing 6. Combining volatile and synchronized to form a "cheap read-write lock"
[highlight=java]@ThreadSafe
public class CheesyCounter {
// Employs the cheap read-write lock trick
// All mutative operations MUST be done with the 'this' lock held
@GuardedBy("this") private volatile int value;

public int getValue() { return value; }

public synchronized int increment() {
return value++;
}
}[/highlight]
 

Wildcard

Top Contributor
Noch zu erwähnen ist:
Singletons braucht man so gut wie nie und von den wenigen die man doch verwendet braucht man fast niemals die Lazy Initialisierung.
 

hdi

Top Contributor
Wird nicht aber in dem Beitrag sogar erwähnt, dass synchronized garantiert, dass die Variable in allen Threads auf die korrekte Instanz zugreift und dass das volatile nur Sinn machen würde bei einer Instanzierung ohne synchronized? Denn wenn ich das recht in Erinnerung haben, ist das synchronized quasi (jetzt nur auf das Problem betrachtet) ein volatile für alle Member-Variablen dieser Klasse.

Ja das frage ich mich jetzt auch. Also nochmal von vorne:

Mit volatile wird der memory nach dem Schreiben geflushed und refreshed,
d.h. nach der Write-Anweisung auf eine volatile Variable hat jeder andere
Thread garantiert den aktuellen Wert.

Das alleine würde aber in meinem Code ganz oben natürlich nicht ausreichen,
weil die Abrage auf null ja von beiden Threads vor einem Schreiben der Instanz
geschehen kann.

Aber wenn ich es synchronisiere, wozu dann volatile?
Erster Thread checkt auf null, ergibt true. Switch.
Zweiter Thread checkt auf null, ergbit true. (Switch optional, kommt auf's gleiche raus)
Erster/Zweiter Thread synchronisiert. Wenn jetzt ein switch passiert kann nix
passieren weil die Variable gesperrt ist.
Auf jeden Fall erzeugt der Thread, der die Synchronisation eingeleitet hat, die Instanz,
und danach ist das Objekt wieder freigeben.
Also betritt der andere Thread den synchronized Block.

So, und jetzt stellt sich eben die Frage, ob der memory nach dem Erzeugen der Instanz
geflushed wurde.

Die Dame im Beitrag sagt, dass volatile mit Synchronisation eh hinfällig wird.
Zusätzlich steht in dem Link zum Video unten ein Kommentar von einem User,
der dort auch schreibt, dass bei Synchronisation automatisch geflushed wird.

Wenn das wahr ist, dann brauch ich volatile folglich hier nicht.

Also... is das nun ein Fehler in meinem Buch? Dort steht halt dabei dass volatile
sicherstellt dass verschiedene Threads diese Variable "korrekt behandeln", wenn
die Instanz erzeugt wird.
Also das im Video angesprochene Problem des Re-Ordering, bzw. Visibility.

...was ja angeblich bei Synchronisation automatisch so ist?!

Man, doofe Situation. Wer weiss das denn nun? Wer hat Lust sich durch
die JLS zu wühlen ^^ Steht das dort überhaupt so genau...
 

fjord

Bekanntes Mitglied
Hab mal noch was dazu gefunden. Synchronization and the Java Memory Model
Visibility
Changes to fields made by one thread are guaranteed to be visible to other threads only under the following conditions:

* A writing thread releases a synchronization lock and a reading thread subsequently acquires that same synchronization lock.
Das Beispiel von dir sollte also meiner Meinung nach ohne volatile funktionieren (mein Beispiel hingegen nicht, weil der lesende Zugriff nicht synchronisiert ist). Ich denke das volatile ist dort aus Performancegründen. Der erste Thread wird den Wert von instance setzen und dann beim Zurückgeben des Locks in den Ram schreiben. Es könnten aber Threads danach kommen, die instance noch als null sehen. Wenn sie dann das Lock erhalten haben, werden sie sehen, dass instance nicht null ist und damit auch kein neues Singleton erstellen. Von der Funktionalität her ist es also auch ohne volatile korrekt meiner Meinung nach. Das volatile erspart den anderen Threads aber das Lock und ich würde stark vermuten, dass Locks teurer sind als volatile.
 

Noctarius

Top Contributor
Soweit ich das Problem verstanden habe braucht man volatile wie weiter oben schon mal erwähnt bei Multicore CPU's oder mehreren CPU's weil hier jeder Kern seinen eigenen Cache hat und der nur mit volatile zwangsweise erneuert wird.

Ich such immer noch den Artikel von neulich dazu.
 

hdi

Top Contributor
@Fjord

Naja so wie ich dein Zitat verstehe brauch ich volatile doch!
Synchronisation heisst nur: Thread B ist gesperrt während Thread A was macht.
Nachdem das Lock freigegeben wird besteht aber noch immer das Visibility Problem,
wie Nocatrius sagt aber wohl nur bei Multicores.
Daher noch ein volatile. (für den Fall die Maschine ist multi-core)

So klingt es zumindest... Es funktioniert nur, wenn man einen synchronized Block hat,
und ist einfach nur eine weitere Absicherung für den Fall von Mutlicores.

D.h. doch aber auch dass ein volatile ohne synchronized keinen Sinn macht...

Und ich denk es geht nicht um die Performance beim Lock vs. Refresh, es ging im Video
darüber dass man Wartezustände vermeiden will, die nicht abbrechbar sind.
Deshalb kein synchronzied. Aber dann macht volatile wieder keinen Sinn, zumindest
wird die Bedingung nicht erfüllt die in deinem Zitat steht.

Gibt's doch nicht... Gibt es eig. keinen Sun Support oder sowas? Steht das denn nirgendwo genau?
Die Leute von Sun müssen das ja wohl zu 100% wissen.
 

mvitz

Top Contributor
...
[HIGHLIGHT="Java"]public class Singleton {

private volatile static Singleton instance;

private Singleton() {
}

public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}[/HIGHLIGHT]

So wie ich volatile verstanden habe, steht es da aus folgendem Grund. Nehmen wir an es gibt 4 CPUs mit 4 verschiedenen Caches. Am Anfang haben alle für instance den Wert null. Jetzt geht der erste hin, macht die erste If-Abfrage, dann Synchroniziert er zur Sicherheit, checkt nochmal ob nicht ein anderer Thread das Objekt schon angelegt hat und erzeugt es dann. Wäre die Variable jetzt nicht volatile, sieht keiner der anderen 3 CPUs, dass instance != null ist. Somit würden diese jetzt jedesmal die erste If-Abfrage mit true beantworten. Beim holen des Locks allerdings würden diese dann feststellen, dass der Wert doch nicht null ist.
Volatile erspart also, dass die 3 anderen CPUs sich den Lock für die Synchronization holen müssen und somit ist das die performanteste Lösung.
 

hdi

Top Contributor
Beim holen des Locks allerdings würden diese dann feststellen, dass der Wert doch nicht null ist.

Wieso beim holen des Locks? Wer sagt das denn?
Die Frage ist: Führt ein Thread beim Beenden eines synchronized,
vor dem Freigeben des Locks, ein refresh des Memory aus?

Falls ja, ist volatile nicht nötig.
Falls nein, wäre jede Synchronisation ohne volatile nicht 100% sicher, denn
ein Thread kann auch bei der 2ten if-Abfrage noch ein null bekommen, obwohl
es das nicht mehr ist, wenn er seinen Cache nicht flushed.

Und zweiteres kann ich mir nicht vorstellen, denn volatile sieht man nie in Programmen, und müsste ja dann bei jedem Billig-Tutorial über Synchronisation
vorkommen.

Ersteres würde ja aber das Keyword volatile überflüssig machen, weil
synchronized "am Ende" den Effekt eines volatiles inkludiert.

Und die Frage, die eig. bisher auch nur von jedem erraten ist:
Hat volatile nur im Zusammenhang mit Multicores Sinn, oder kann es
diese Re-Ordering/Visibility Probleme auch bei 1 CPU mit 2+Threads geben?
Hat jeder Thread seinen eigenen Cache-Bereich (auch bei 1 CPU), oder greifen
Threads innerhalb einer CPU direkt auf das "Original" zu?
 

mvitz

Top Contributor
Wieso beim holen des Locks? Wer sagt das denn?
Die Frage ist: Führt ein Thread beim Beenden eines synchronized,
vor dem Freigeben des Locks, ein refresh des Memory aus?

Falls ja, ist volatile nicht nötig.
Falls nein, wäre jede Synchronisation ohne volatile nicht 100% sicher, denn
ein Thread kann auch bei der 2ten if-Abfrage noch ein null bekommen, obwohl
es das nicht mehr ist, wenn er seinen Cache nicht flushed.
...

In dem JAX-TV Vortrag (den habe ich mir gestern angeguckt) sagt sie eindeutig, dass nur Threads (CPUs) die Veränderung innerhalb eines Synchronized Blocks sehen, wenn sie denselben Lock erhalten. Volatile allerdings, garantiert, dass bei einem lesenden Zugriff der Memory geflushed wird. Somit sehen spätere Threads garantiert, dass deine Instance != null ist, bevor sie den Lock holen müssen (was teurer ist, als den Memoryflsuh per volatile).

Wenn also eh alle Threads durch deinen synchronized Block müssen, dann kannst du dir volatile auch sparen. Da dies dann häuifg der Fall ist, sieht man volatile auch recht selten. Aber das oben dargestellte Double-Check-Idiom macht nur mit volatile Sinn.
 

fjord

Bekanntes Mitglied
[...] Synchronisation heisst nur: Thread B ist gesperrt während Thread A was macht.
Nachdem das Lock freigegeben wird besteht aber noch immer das Visibility Problem, [...]
Synchronisation ist auch Visibility. Du hast insofern recht, dass das Problem noch dann besteht, wenn Thread A das Lock freigibt, sobald sich aber Thread B das Lock holt ist dir garantiert, dass Thread B alle Änderungen von Thread A sieht.

volatile ist im Grunde keine Ergänzung zu synchronized. Du könntest alles wo volatile benutzt wird auch mit sychnronized erreichen.
Es macht aber durchaus Sinn volatile zu benutzen, weil es günstiger ist als ein Lock zu aquirieren, außerdem musst du bei einem Lock warten, wenns bereits jemand anderes hat, bei volatile nicht. In dem Beispiel das ich gepostet habe wird die Visibility alleine durch volatile erreicht, synchronized ist nur da um die Atomität von value++ zu erreichen.

Wieso beim holen des Locks? Wer sagt das denn?
Die Frage ist: Führt ein Thread beim Beenden eines synchronized,
vor dem Freigeben des Locks, ein refresh des Memory aus? [...]
Das Zitat von mir sagt es. Ja, der Thread refresh das Memory beim freigeben des Locks, er refresht damit aber nicht die Caches der anderen Threads. Die übernehmen den Wert erst, wenn sie dasselbe Lock erhalten. Insofern würde in deinem Beispiel ohne volatile die erste Abfrage instance == null keinen Sinn machen, weil sie mit hoher Wahrscheinlichkeit immer true ergibt.
 

fjord

Bekanntes Mitglied
Natürlich, da stimme ich dir zu.
Es ging dabei aber ja darum zu zeigen wie volatile und synchronized zusammenarbeiten können.

Die Funktionsweise von AtomicInteger ist auch nicht so sehr anders als die aus meinem Beispiel. Es wird auch ein volatile int benutzt um den Wert zu halten, allerdings wird auf trickreiche Art die Lockaquisition umgangen.
 

KSG9|sebastian

Top Contributor
Wobei man sich auch überlegen muss ob das volatile an jeder Stelle Sinn macht.
Vorteil ist ganz klar "lock-free"-programming. Allerding kann ein zu häufig ausgelöster Flush auch Performancenachteile bringen welche dann die verwendung eines synchronized wieder rechtfertigen.

Ne andere Möglichkeit sowas zu tun ist (sofern es in dem Kontext geht) die Klasse und alle Instanzvariablen final zu machen. Damit hat man auch die Garantie dass die Sichtbarkeit für alle Threads gewährleistet wird.
Allerdings trifft dass nur zu wenn _alle_ Instanzvariablen auf final stehen.

Code:
class Singleton{
   private final static Instance inst = new Instance(5, new Child(a...));
}

final class Instance{
   private final Integer x;
   // auch alle Instanzvariablen von Child müssen final sein
   private final Child child;

   public Instance(Integer x, Child child){
      this.x = x;
      this.child = child;
   }
}
 

fjord

Bekanntes Mitglied
[...] Allerding kann ein zu häufig ausgelöster Flush auch Performancenachteile bringen welche dann die verwendung eines synchronized wieder rechtfertigen. [...]
synchronized muss immer flushen, sonst könnte es nicht garantieren, dass Änderungen zwischen 2 Threads die nacheinander denselben Lock halten sichtbar sind.
 
Status
Nicht offen für weitere Antworten.

Ähnliche Java Themen

Neue Themen


Oben