S
SlaterB
Gast
hallo,
ich finde den Artikel
The "Double-Checked Locking is Broken" Declaration
nicht genau genug (edit: bzw. möchte nicht alle referenzierten Links auch noch lesen
),
ich kann mir gut vorstellen dass er richtig ist, wäre mit deutlicheren Erklärungen aber noch zufriedener,
es geht um 'lazy initialization in a multithreaded environment', also mit Synchronisation
die erste ernstzunehmende Version
wird abgelehnt mit (nach meinem Verständnis) der Begründung, dass die Variable helper schon gesetzt wird,
bevor der Konstruktor von Helper komplett ausgeführt wird,
nach welchen internen Optimierungsgründen auch immer, das finde ich nicht schön, kann ich aber halbwegs akzeptieren,
da es aus lokaler Sichtweise hinkommt, bevor die nächste Code-Zeile drankommt wird schon alles fertig sein,
Reihenfolge wäre für nur einen Thread relativ egal
-----
das folgende Beispiel im Artikel ist mir dann schon bisschen zu kompliziert,
enthält nebenher auch langweilige doppelte Synchronisation,
der spannendere Teil ist für mich die Nutzung einer lokalen Variable, ein verkürzter Code:
die ablehnende Begründung im Artikel kümmert sich mehr um die doppelte Synchronisation usw.,
ich frage mich aber ob nicht allein die lokale Variable hilft, das Problem des ersten Beispiels zu beheben?
kann nicht in Zeile 9 davon ausgegangen werden dass das Objekt h komplett initialisiert ist?
denn wenn nicht, dann hätte man ja bei jedem lokal erzeugten Objekt das Problem, dass es in der nächsten Zeile vielleicht noch nicht sauber erstellt ist..,
in speziell diesem Fall kann man vielleicht wieder böse Optimierungen vermuten, etwa dass die lokale Variable h komplett rausfällt
und der Code exakt zur Variante 1 'verbessert' wird, ist das noch das verbleibende Problem?
aber man kann zwischen Zeile 8 und 9 ja noch mehr Code einfügen, z.B. eine Ausgabe der Attribute oder hashCode()-Aufruf,
danach richten sich doch hoffentlich die denkbaren Befehlsvertauschungen?
strenggenommen wäre dann denkbar, dass der Code immer noch zu
optimiert wird?,
mit den gleichen Problemen für andere Threads am Anfang mit uninitialisierten this.helper
aber durchaus funktionierenden Ausgaben später
-----
dann würde ich weiterdenken:
Hilfsmethoden an sich helfen vielleicht nicht, immer mit dem Totschlagargument dass deren Code einfach herüberkopiert wird..,
eine Initialisierungsklasse wäre ziemlich heftig, mit Konstruktor und get-Methode, das kann doch kaum alles herausgezogen werden,
da ich aber danach noch andere Ideen hatte erspare ich erst mal den langen Code mit Erklärungen dazu
zurück nur zur Klasse Foo, nur getHelper dort, in synchronized im if zu den entscheidenen Zeilen:
soll schlecht sein
soll auch schlecht sein
wie steht es mit folgendem:
mit einer Methode returnYourself() die nur sich selbst, also this zurückgibt,
kann dieser eigentlich sinnlose Methodenaufruf wegoptimiert werden?
wenn nicht, kann die Methode ausgeführt werden bevor das Helper-Objekt vollständig initialisiert ist?
und hoffentlich steht doch wohl in der Variable this.helper nichts drin, bevor nicht die Methode returnYourself() ausgeführt wurde?
oder noch eine Variante:
mal abgesehen von der Frage ob der Hashcode vielleicht doch Integer.MAX_VALUE ist,
ist es irgendwie denkbar dass die Methode hashCode() vor kompletter Initialisierung von h ausgeführt wird?
oder auf andere Weise der ganze Block übersprungen wird?
-------
dann noch die Möglichkeit, das Objekt nicht direkt abzulegen sondern in einer List/ Map zu speichern,
Java wird doch wohl nicht wagen, dorthin ein uninitialisiertes Objekt zu übergeben?
warum wird im Artikel soweit ich das sehe auf derartiges nicht eingegangen?
die Varianten mit ThreadLocal oder Guard<LOCK> scheinen mir höchst kompliziert,
als wäre generell nichts möglich und nur zum Spaß werden akademische Versionen gezeigt
volatile und Immutable sind am Ende immerhin noch saubere Lösungen
ich finde den Artikel
The "Double-Checked Locking is Broken" Declaration
nicht genau genug (edit: bzw. möchte nicht alle referenzierten Links auch noch lesen
ich kann mir gut vorstellen dass er richtig ist, wäre mit deutlicheren Erklärungen aber noch zufriedener,
es geht um 'lazy initialization in a multithreaded environment', also mit Synchronisation
die erste ernstzunehmende Version
Java:
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (this.helper == null) {
synchronized (this) {
if (this.helper == null) {
this.helper = new Helper();
}
}
}
return this.helper;
}
}
bevor der Konstruktor von Helper komplett ausgeführt wird,
nach welchen internen Optimierungsgründen auch immer, das finde ich nicht schön, kann ich aber halbwegs akzeptieren,
da es aus lokaler Sichtweise hinkommt, bevor die nächste Code-Zeile drankommt wird schon alles fertig sein,
Reihenfolge wäre für nur einen Thread relativ egal
-----
das folgende Beispiel im Artikel ist mir dann schon bisschen zu kompliziert,
enthält nebenher auch langweilige doppelte Synchronisation,
der spannendere Teil ist für mich die Nutzung einer lokalen Variable, ein verkürzter Code:
Java:
class Foo {
private Helper helper = null;
public Helper getHelper() {
if (this.helper == null) {
synchronized (this) {
if (this.helper == null) {
Helper h = new Helper();
this.helper = h;
}
}
}
return helper;
}
}
ich frage mich aber ob nicht allein die lokale Variable hilft, das Problem des ersten Beispiels zu beheben?
kann nicht in Zeile 9 davon ausgegangen werden dass das Objekt h komplett initialisiert ist?
denn wenn nicht, dann hätte man ja bei jedem lokal erzeugten Objekt das Problem, dass es in der nächsten Zeile vielleicht noch nicht sauber erstellt ist..,
in speziell diesem Fall kann man vielleicht wieder böse Optimierungen vermuten, etwa dass die lokale Variable h komplett rausfällt
und der Code exakt zur Variante 1 'verbessert' wird, ist das noch das verbleibende Problem?
aber man kann zwischen Zeile 8 und 9 ja noch mehr Code einfügen, z.B. eine Ausgabe der Attribute oder hashCode()-Aufruf,
danach richten sich doch hoffentlich die denkbaren Befehlsvertauschungen?
strenggenommen wäre dann denkbar, dass der Code immer noch zu
Java:
this.helper = new Helper();
// Ausgaben oder irgendein Aufruf an this.helper
mit den gleichen Problemen für andere Threads am Anfang mit uninitialisierten this.helper
aber durchaus funktionierenden Ausgaben später
-----
dann würde ich weiterdenken:
Hilfsmethoden an sich helfen vielleicht nicht, immer mit dem Totschlagargument dass deren Code einfach herüberkopiert wird..,
eine Initialisierungsklasse wäre ziemlich heftig, mit Konstruktor und get-Methode, das kann doch kaum alles herausgezogen werden,
da ich aber danach noch andere Ideen hatte erspare ich erst mal den langen Code mit Erklärungen dazu
zurück nur zur Klasse Foo, nur getHelper dort, in synchronized im if zu den entscheidenen Zeilen:
Java:
this.helper = new Helper();
Java:
Helper h = new Helper();
this.helper = h;
wie steht es mit folgendem:
Java:
this.helper = new Helper().returnYourself();
kann dieser eigentlich sinnlose Methodenaufruf wegoptimiert werden?
wenn nicht, kann die Methode ausgeführt werden bevor das Helper-Objekt vollständig initialisiert ist?
und hoffentlich steht doch wohl in der Variable this.helper nichts drin, bevor nicht die Methode returnYourself() ausgeführt wurde?
oder noch eine Variante:
Java:
Helper h = new Helper();
this.helper = h.hashCode() < Integer.MAX_VALUE ? h : null;
ist es irgendwie denkbar dass die Methode hashCode() vor kompletter Initialisierung von h ausgeführt wird?
oder auf andere Weise der ganze Block übersprungen wird?
-------
dann noch die Möglichkeit, das Objekt nicht direkt abzulegen sondern in einer List/ Map zu speichern,
Java wird doch wohl nicht wagen, dorthin ein uninitialisiertes Objekt zu übergeben?
warum wird im Artikel soweit ich das sehe auf derartiges nicht eingegangen?
die Varianten mit ThreadLocal oder Guard<LOCK> scheinen mir höchst kompliziert,
als wäre generell nichts möglich und nur zum Spaß werden akademische Versionen gezeigt
volatile und Immutable sind am Ende immerhin noch saubere Lösungen
Zuletzt bearbeitet von einem Moderator: