0

Vorwort: Ich weiß, dass in den meisten Fällen mit einem volatilen Feld keine messbare Leistungseinbußen, aber diese Frage ist mehr theoretisch und zielgerichtet auf ein Design mit einem extrem hohe currency-Unterstützung.Java gleichzeitiger Zugriff auf Feld, Trick nicht flüchtig zu verwenden

Ich habe ein Feld, das eine List<Something> ist, die nach der Erstellung gefüllt wird. Um etwas Leistung zu sparen, möchte ich die Liste in eine schreibgeschützte Map umwandeln. Wenn Sie dies zu einem beliebigen Zeitpunkt tun möchten, benötigen Sie mindestens ein flüchtiges Map-Feld, damit Änderungen für alle Threads sichtbar werden.

Ich dachte an den folgenden Aktionen ausführen:

 
Map map; 

public void get(Object key){ 
    if(map==null){ 
     Map temp = new Map(); 
     for(Object value : super.getList()){ 
      temp.put(value.getKey(),value); 
     } 
     map = temp; 
    } 
    return map.get(key); 
} 

Dies könnte mehrere Threads verursacht die Karte selbst erzeugen, wenn sie den get-Block in einer serialisierten Weise eingeben. Dies wäre kein großes Problem, wenn Threads auf verschiedenen identischen Instanzen der Map arbeiten. Was mich mehr beunruhigt ist:

Ist es möglich, dass ein Thread die neue temporäre Karte dem Kartenfeld zuweist, und dann ein zweiter Thread sieht map!=null und daher Zugriff auf das Kartenfeld, ohne eine neue, aber zu meiner Überraschung stellt fest, dass die Karte leer ist, weil die Put-Operationen noch nicht in einen gemeinsamen Speicherbereich verschoben wurden?

Antworten auf Kommentare:

  • Die Fäden die temporäre Karte nach nur ändern, dass es nur gelesen wird.
  • Ich muss eine Liste in eine Karte umwandeln wegen einer speziellen JAXB-Konfiguration, die es nicht möglich macht, eine Karte zu erstellen.
+0

'volatile' können einige Kosten haben vor allem in Multi-Prozessor-Umgebungen. Es kann Speichersperren erfordern, um sicherzustellen, dass andere Threads Änderungen sehen. – seand

+0

Können Sie erklären, wie/wann Threads welche Liste/Karte ändern und warum Sie eine Liste in eine Karte kopieren möchten? – zapl

+0

Ich sehe kein Problem (andere, dass Ihr Code nicht kompilieren wird, kein Rückgabetyp) –

Antwort

3

Ist es möglich, dass ein Thread die neue temporäre Karte auf die Karte Feld zuweist, und dann ein zweites Gewinde sieht, dass map!=null und damit das Kartenfeld zuzugreift, ohne einen neuen zu erzeugen, aber zu meiner Überraschung fest, dass die Karte ist leer, weil die put-Operationen noch nicht in einen gemeinsamen Speicherbereich verschoben wurden?

Ja, das ist absolut möglich; Zum Beispiel könnte ein optimierender Compiler tatsächlich die lokale temp Variable loswerden und nur das map Feld die ganze Zeit verwenden, vorausgesetzt es map zu null im Falle einer Ausnahme wiederhergestellt.

In ähnlicher Weise könnte ein Thread auch eine nicht leere, nicht leere map anzeigen, die dennoch nicht vollständig ausgefüllt ist. Und wenn Ihre Klasse Map nicht sorgfältig entworfen wurde, um gleichzeitige Lese- und Schreibvorgänge zuzulassen (oder synchronized verwendet, um das Problem zu vermeiden), können Sie auch bizarres Verhalten erhalten, wenn ein Thread seine get-Methode aufruft, während ein anderer seine put aufruft.

+1

Dies ist, wie vorsichtig Sie sein müssen: http://mailinator.blogspot.co.uk/2009/06/beautiful-race-condition.html – biziclop

+0

@biziclop Interessant, ich habe gesehen, dass ein paar Mal passieren, ist es gut um eine Erklärung zu sehen. –

+1

@biziclop: Das * ist * interessant, aber seine Relevanz für den OP-Code ist subtil. Diese spezielle Race-Bedingung kann nur auftreten, wenn zwei verschiedene Threads gleichzeitig auf derselben Karte "Put" aufrufen, was im OP-Code nicht möglich sein soll. (Es kann jedoch * im Code des OP vorkommen, wenn der Compiler die 'temp'-Variable eliminiert, und wenn zwei Threads 'map == null' sehen, so setzen beide' map' auf eine neue Map und beginnen mit dem Hinzufügen von Elementen.) – ruakh

1

Können Sie Ihre Karte im ctor erstellen und als final deklarieren? Vorausgesetzt, Sie verlieren die Karte nicht, so dass andere sie ändern können, sollte dies ausreichen, um Ihre get() sicher durch mehrere Threads gemeinsam nutzbar zu machen.

+0

Ich konnte es nur final machen, wenn ich ein hässliches Delegationsmuster verwende und die List durch den Konstruktor übergebe, was ich für diese Art von Klassen nicht tun möchte. Die Liste wird von JAXB generiert und ich möchte in einem zweiten Schritt in einer Klasse, die von der Basisklasse erbt, die die Liste enthält, eine Zuordnung erstellen. –

+0

Ich glaube, das Erstellen Ihrer Karte in der CTor und das Markieren der endgültigen ist das einzige, was Sie tun können, um die Synchronisierung sicher zu vermeiden. Wie wäre es mit einer anderen Klasse, um die Liste von JAXB zu erstellen und dann übergeben Sie es unserer Orig Klasse Ctor? – seand

+0

Ja, das würde funktionieren, aber mein Code würde wirklich aufblähen und die automatische Beanerzeugung würde ihren Punkt verfehlt haben. Ich denke, ich werde für ein flüchtiges Feld für das Wurzelobjekt gehen. Jeder andere Thread, der auf ein Objekt zugreift, das irgendwie mit dem Root verknüpft ist, wird dann gezwungen, jedes gemeinsam genutzte Objekt zu aktualisieren. –

0

Wenn Sie wirklich Zweifel haben, ob ein anderer Thread eine "halb fertig" Karte lesen könnte (glaube ich nicht, aber sagen Sie nie nie ;-), können Sie dies versuchen.

Karte ist null oder vollständige

static class MyMap extends HashMap { 
    MyMap (List pList) { 
    for(Object value : pList){ 
     put(value.getKey(), value); 
    } 
    } 
} 

MyMap map; 

public Object get(Object key){ 
    if(map==null){ 
     map = new MyMap (super.getList()); 
    } 
    return map.get(key); 
} 

Oder jemand ein neues eingeführtes Problem nicht sehen?

+1

Das hat das gleiche Problem: ein anderer Thread kann die Zuordnung zu "map" sehen, bevor der Konstruktor fertig ist. Sehen Sie http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html, suchen Sie auf der Seite nach "Symantec". – ruakh

+0

Nun, wenn Sie die Fakten nicht glauben wollen, kann ich Sie sicherlich nicht dazu zwingen. : -/ – ruakh

+0

Ich habe den Absatz gefunden, in dem das steht. Dies bedeutet, dass jedes "neue" synchronisiert werden muss, sonst besteht die Gefahr, dass Sie mit einem unvollständigen Objekt arbeiten. Also ist "neu" nicht atomar. –

0

Zusätzlich zu den oben genannten Sichtbarkeitsproblemen gibt es ein weiteres Problem mit dem ursprünglichen Code, nämlich. es kann hier eine Nullpointer werfen:

return this.map.get(key) 

Welche kontraintuitiv ist, aber das ist, was man von falsch synchronisierten Code erwarten.

Beispielcode, dies zu verhindern:

Map temp; 
if ((temp = this.map) == null) 
{ 

    temp = new ImmutableMap(getList()); 
    this.map = temp; 
} 
return temp.get(key);