Die Notwendigkeit eines flüchtigen Modifikators für die doppelte Überprüfung der Sperrung in .NET

85

In mehreren Texten heißt es, dass bei der Implementierung einer doppelt aktivierten Sperre in .NET auf das Feld, auf das Sie sperren, ein flüchtiger Modifikator angewendet werden sollte. Aber warum genau? Betrachtet man das folgende Beispiel:

public sealed class Singleton
{
   private static volatile Singleton instance;
   private static object syncRoot = new Object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         if (instance == null) 
         {
            lock (syncRoot) 
            {
               if (instance == null) 
                  instance = new Singleton();
            }
         }

         return instance;
      }
   }
}

Warum erreicht "lock (syncRoot)" nicht die erforderliche Speicherkonsistenz? Stimmt es nicht, dass nach der "Lock" -Anweisung sowohl Lesen als auch Schreiben flüchtig sind und somit die erforderliche Konsistenz erreicht wird?

Konstantin
quelle
2
Dies wurde schon oft gekaut. yoda.arachsys.com/csharp/singleton.html
Hans Passant
1
Leider verweist Jon in diesem Artikel zweimal auf "flüchtig", und keiner der Verweise verweist direkt auf die von ihm angegebenen Codebeispiele.
Dan Esparza
Lesen Sie diesen Artikel, um das Problem zu verstehen: igoro.com/archive/volatile-keyword-in-c-memory-model-explained Grundsätzlich ist es der JIT theoretisch möglich, ein CPU-Register für die Instanzvariable zu verwenden - insbesondere, wenn Sie eine benötigen wenig zusätzlicher Code drin. Wenn Sie also eine if-Anweisung zweimal ausführen, kann dies möglicherweise denselben Wert zurückgeben, unabhängig davon, ob er sich in einem anderen Thread ändert. In Wirklichkeit ist die Antwort ein wenig komplex. Die Lock-Anweisung kann dafür verantwortlich sein oder auch nicht, die Dinge hier zu
verbessern
(siehe vorherigen Kommentar, Fortsetzung) - Folgendes passiert meiner Meinung nach wirklich - Grundsätzlich kann jeder Code, der etwas Komplexeres als das Lesen oder Setzen einer Variablen ausführt, die JIT dazu veranlassen, zu sagen: Vergessen Sie den Versuch, dies zu optimieren. Laden Sie ihn einfach und speichern Sie ihn im Speicher, weil Wenn eine Funktion aufgerufen wird, muss die JIT möglicherweise das Register speichern und neu laden, wenn sie es jedes Mal dort speichert, anstatt jedes Mal direkt aus dem Speicher zu schreiben und zu lesen. Woher weiß ich, dass Schloss nichts Besonderes ist? Schauen Sie sich den Link an, den ich im vorherigen Kommentar von Igor gepostet habe (Fortsetzung nächster Kommentar)
user2685937
(siehe oben 2 Kommentare) - Ich habe den Code von Igor getestet und als er einen neuen Thread erstellt hat, habe ich eine Sperre hinzugefügt und sogar eine Schleife erstellt. Es würde immer noch nicht dazu führen, dass der Code beendet wird, da die Instanzvariable aus der Schleife gehoben wurde. Durch Hinzufügen eines einfachen lokalen Variablensatzes zur while-Schleife wurde die Variable immer noch aus der Schleife gehoben. Jetzt würde alles, was komplizierter ist, als wenn Anweisungen oder ein Methodenaufruf oder ja sogar ein Sperraufruf die Optimierung verhindern und damit funktionieren würden. Daher erzwingt jeder komplexe Code häufig den direkten Variablenzugriff, anstatt die JIT optimieren zu lassen. (Fortsetzung nächster Kommentar)
user2685937

Antworten:

59

Flüchtig ist nicht erforderlich. Naja, so ungefähr**

volatilewird verwendet, um eine Speicherbarriere * zwischen Lese- und Schreibvorgängen für die Variable zu erstellen.
lockBei Verwendung werden Speicherbarrieren um den Block innerhalb des Blocks erstellt und der lockZugriff auf den Block auf einen Thread beschränkt.
Speicherbarrieren sorgen dafür, dass jeder Thread den aktuellsten Wert der Variablen liest (kein lokaler Wert, der in einem Register zwischengespeichert ist) und dass der Compiler die Anweisungen nicht neu anordnet. Die Verwendung volatileist nicht erforderlich **, da Sie bereits eine Sperre haben.

Joseph Albahari erklärt dieses Zeug viel besser als ich es jemals könnte.

Lesen Sie auch Jon Skeets Leitfaden zur Implementierung des Singletons in C #


update :
* volatilebewirkt, dass Lesevorgänge der Variablen VolatileReads und Schreibvorgänge VolatileWrites sind, die unter x86 und x64 unter CLR mit a implementiert sind MemoryBarrier. Sie können auf anderen Systemen feinkörniger sein.

** Meine Antwort ist nur richtig, wenn Sie die CLR auf x86- und x64-Prozessoren verwenden. Es könnte in anderen Speichermodellen, wie auf Mono (und anderen Implementierungen), Itanium64 und zukünftige Hardware wahr sein. Darauf bezieht sich Jon in seinem Artikel in den "Fallstricken" für doppelt geprüftes Sperren.

Möglicherweise ist es erforderlich , die Variable als {Markieren der Variablen als volatile, Lesen mit Thread.VolatileReadoder Einfügen eines Aufrufs an Thread.MemoryBarrier} auszuführen, damit der Code in einer schwachen Speichermodellsituation ordnungsgemäß funktioniert.

Soweit ich weiß, werden Schreibvorgänge in der CLR (auch auf IA64) nie neu angeordnet (Schreibvorgänge haben immer eine Release-Semantik). Auf IA64 können Lesevorgänge jedoch vor dem Schreiben neu angeordnet werden, es sei denn, sie sind als flüchtig gekennzeichnet. Leider habe ich keinen Zugriff auf IA64-Hardware, mit der ich spielen kann. Alles, was ich dazu sage, wäre Spekulation.

Ich fand diese Artikel auch hilfreich:
http://www.codeproject.com/KB/tips/MemoryBarrier.aspx
vance morrisons artikel (alles verlinkt darauf, es geht um doppelt geprüftes sperren)
chris brummes artikel (alles verlinkt darauf )
Joe Duffy: Defekte Varianten der doppelt überprüften Verriegelung

luis abreus serie zum multithreading gibt auch einen schönen überblick über die konzepte
http://msmvps.com/blogs/luisabreu/archive/2009/06/29/multithreading-load-and-store-reordering.aspx
http: // msmvps. com / Blogs / Luisabreu / Archiv / 2009/07/03 / Multithreading-Einführung-Memory-Fences.aspx

Dan
quelle
Jon Skeet sagt tatsächlich, dass ein flüchtiger Modifikator erforderlich ist, um einen korrekten Speicherbarier zu erstellen, während der erste Linkautor sagt, dass eine Sperre (Monitor.Enter) ausreichend wäre. Wer hat eigentlich recht ???
Konstantin
@Konstantin Es scheint, dass Jon sich auf das Speichermodell auf Itanium 64-Prozessoren bezog. In diesem Fall kann die Verwendung von flüchtigen Stoffen erforderlich sein. Auf x86- und x64-Prozessoren ist jedoch keine Flüchtigkeit erforderlich. Ich werde gleich mehr aktualisieren.
Dan
Wenn die Sperre tatsächlich eine Speicherbarriere bildet und bei der Speicherbarier tatsächlich sowohl die Befehlsreihenfolge als auch der Cache ungültig werden, sollte dies auf allen Prozessoren funktionieren. Wie auch immer, es ist so seltsam, dass so eine grundlegende Sache so viel Verwirrung
Konstantin
2
Diese Antwort sieht für mich falsch aus. Wenn volatileauf unnötig war jede Plattform dann würde es bedeuten , die JIT nicht die Speicherlasten optimieren könnte , object s1 = syncRoot; object s2 = syncRoot;um object s1 = syncRoot; object s2 = s1;auf dieser Plattform. Das scheint mir sehr unwahrscheinlich.
user541686
1
Selbst wenn die CLR die Schreibvorgänge nicht neu anordnen würde (ich bezweifle, dass dies zutrifft, können auf diese Weise viele sehr gute Optimierungen vorgenommen werden), wäre sie dennoch fehlerhaft, solange wir den Konstruktoraufruf inline einfügen und das Objekt direkt erstellen können (Wir konnten ein halb initialisiertes Objekt sehen). Unabhängig von einem Speichermodell, das die zugrunde liegende CPU verwendet! Laut Eric Lippert führt die CLR auf Intel zumindest eine Membarriere nach Konstruktoren ein, die diese Optimierung leugnet, aber das wird von der Spezifikation nicht verlangt, und ich würde nicht damit rechnen, dass zum Beispiel auf ARM dasselbe passiert.
Voo
34

Es gibt eine Möglichkeit, es ohne volatileFeld zu implementieren . Ich werde es erklären ...

Ich denke, dass die Neuordnung des Speicherzugriffs innerhalb des Schlosses gefährlich ist, sodass Sie eine nicht vollständig initialisierte Instanz außerhalb des Schlosses erhalten können. Um dies zu vermeiden, mache ich Folgendes:

public sealed class Singleton
{
   private static Singleton instance;
   private static object syncRoot = new Object();

   private Singleton() {}

   public static Singleton Instance
   {
      get 
      {
         // very fast test, without implicit memory barriers or locks
         if (instance == null)
         {
            lock (syncRoot)
            {
               if (instance == null)
               {
                    var temp = new Singleton();

                    // ensures that the instance is well initialized,
                    // and only then, it assigns the static variable.
                    System.Threading.Thread.MemoryBarrier();
                    instance = temp;
               }
            }
         }

         return instance;
      }
   }
}

Den Code verstehen

Stellen Sie sich vor, der Konstruktor der Singleton-Klasse enthält einen Initialisierungscode. Wenn diese Anweisungen neu angeordnet werden, nachdem das Feld mit der Adresse des neuen Objekts festgelegt wurde, liegt eine unvollständige Instanz vor. Stellen Sie sich vor, die Klasse hat diesen Code:

private int _value;
public int Value { get { return this._value; } }

private Singleton()
{
    this._value = 1;
}

Stellen Sie sich nun einen Aufruf des Konstruktors mit dem neuen Operator vor:

instance = new Singleton();

Dies kann auf folgende Operationen erweitert werden:

ptr = allocate memory for Singleton;
set ptr._value to 1;
set Singleton.instance to ptr;

Was ist, wenn ich diese Anweisungen wie folgt nachbestelle:

ptr = allocate memory for Singleton;
set Singleton.instance to ptr;
set ptr._value to 1;

Macht es einen Unterschied? NEIN, wenn Sie an einen einzelnen Thread denken. JA, wenn Sie an mehrere Threads denken ... was ist, wenn der Thread kurz danach unterbrochen wird set instance to ptr:

ptr = allocate memory for Singleton;
set Singleton.instance to ptr;
-- thread interruped here, this can happen inside a lock --
set ptr._value to 1; -- Singleton.instance is not completelly initialized

Dies wird durch die Speicherbarriere vermieden, indem keine Neuordnung des Speicherzugriffs zugelassen wird:

ptr = allocate memory for Singleton;
set temp to ptr; // temp is a local variable (that is important)
set ptr._value to 1;
-- memory barrier... cannot reorder writes after this point, or reads before it --
-- Singleton.instance is still null --
set Singleton.instance to temp;

Viel Spaß beim Codieren!

Miguel Angelo
quelle
1
Wenn die CLR vor der Initialisierung den Zugriff auf ein Objekt ermöglicht, ist dies eine Sicherheitslücke. Stellen Sie sich eine privilegierte Klasse vor, deren einziger öffentlicher Konstruktor "SecureMode = 1" setzt und deren Instanzmethoden dies dann überprüfen. Wenn Sie diese Instanzmethoden aufrufen können, ohne dass der Konstruktor ausgeführt wird, können Sie aus dem Sicherheitsmodell ausbrechen und das Sandboxing verletzen.
MichaelGG
1
@MichaelGG: In dem von Ihnen beschriebenen Fall ist es ein Problem, wenn diese Klasse mehrere Threads unterstützt, um darauf zuzugreifen. Wenn der Konstruktoraufruf vom Jitter eingefügt wird, kann die CPU Anweisungen so neu anordnen, dass die gespeicherte Referenz auf eine nicht vollständig initialisierte Instanz verweist. Dies ist kein CLR-Sicherheitsproblem, da es vermeidbar ist. Es liegt in der Verantwortung des Programmierers,: Interlocked, Speicherbarrieren, Sperren und / oder flüchtige Felder innerhalb des Konstruktors einer solchen Klasse zu verwenden.
Miguel Angelo
2
Eine Barriere im Ctor repariert es nicht. Wenn die CLR den Verweis dem neu zugewiesenen Objekt zuweist, bevor der CTOR abgeschlossen ist, und keinen Membarrier einfügt, kann ein anderer Thread eine Instanzmethode für ein halbinitialisiertes Objekt ausführen.
MichaelGG
Dies ist das "alternative Muster", das ReSharper 2016/2017 im Fall einer DCL in C # vorschlägt. OTOH, Java tut Garantie dafür , dass das Ergebnis newvollständig initialisiert ..
user2864740
Ich weiß, dass die Implementierung von MS .net eine Speicherbarriere am Ende des Construtors platziert ... aber es ist besser, auf Nummer sicher zu gehen.
Miguel Angelo
7

Ich glaube nicht, dass jemand die Frage tatsächlich beantwortet hat , also werde ich es versuchen.

Das Flüchtige und das Erste if (instance == null)sind nicht "notwendig". Die Sperre macht diesen Code threadsicher.

Die Frage ist also: Warum sollten Sie die erste hinzufügen if (instance == null)?

Der Grund ist vermutlich, zu vermeiden, dass der gesperrte Codeabschnitt unnötig ausgeführt wird. Während Sie den Code innerhalb der Sperre ausführen, wird jeder andere Thread blockiert, der versucht, diesen Code ebenfalls auszuführen. Dies verlangsamt Ihr Programm, wenn Sie versuchen, häufig von vielen Threads aus auf den Singleton zuzugreifen. Abhängig von der Sprache / Plattform kann es auch zu Overheads des Schlosses kommen, die Sie vermeiden möchten.

Die erste Nullprüfung wird also hinzugefügt, um schnell festzustellen, ob Sie die Sperre benötigen. Wenn Sie den Singleton nicht erstellen müssen, können Sie die Sperre vollständig umgehen.

Sie können jedoch nicht überprüfen, ob die Referenz null ist, ohne sie auf irgendeine Weise zu sperren, da sie aufgrund des Prozessor-Caching von einem anderen Thread geändert werden könnte und Sie einen "veralteten" Wert lesen würden, der dazu führen würde, dass Sie die Sperre unnötig betreten. Aber Sie versuchen, eine Sperre zu vermeiden!

Sie machen den Singleton also flüchtig, um sicherzustellen, dass Sie den neuesten Wert lesen, ohne eine Sperre verwenden zu müssen.

Sie benötigen weiterhin das innere Schloss, da flüchtig Sie nur während eines einzelnen Zugriffs auf die Variable schützt - Sie können es nicht sicher testen und einstellen, ohne ein Schloss zu verwenden.

Ist das wirklich nützlich?

Nun, ich würde sagen "in den meisten Fällen nein".

Wenn Singleton.Instance aufgrund der Sperren zu Ineffizienz führen kann, warum rufen Sie es dann so häufig auf, dass dies ein erhebliches Problem darstellt ? Der springende Punkt eines Singletons ist, dass es nur einen gibt, sodass Ihr Code die Singleton-Referenz einmal lesen und zwischenspeichern kann.

Der einzige Fall, in dem ich mir vorstellen kann, wo dieses Caching nicht möglich wäre, wäre, wenn Sie eine große Anzahl von Threads haben (z. B. könnte ein Server, der einen neuen Thread zum Verarbeiten jeder Anforderung verwendet, Millionen von sehr kurz laufenden Threads erstellen, von denen jeder die Singleton.Instance einmal aufrufen müsste).

Ich vermute also, dass doppelt geprüftes Sperren ein Mechanismus ist, der in ganz bestimmten leistungskritischen Fällen einen echten Platz hat, und dann sind alle auf den Zug "Dies ist der richtige Weg, dies zu tun" geklettert, ohne wirklich darüber nachzudenken, was er tut und ob er es tut wird tatsächlich notwendig sein, wenn sie es für verwenden.

Jason Williams
quelle
6
Dies liegt irgendwo zwischen falsch und dem Fehlen des Punktes. volatilehat nichts mit der Sperrsemantik beim doppelt überprüften Sperren zu tun, sondern mit dem Speichermodell und der Cache-Kohärenz. Damit soll sichergestellt werden, dass ein Thread keinen Wert erhält, der noch von einem anderen Thread initialisiert wird, was durch das Double-Check-Sperrmuster nicht von Natur aus verhindert wird. In Java benötigen Sie auf jeden Fall das volatileSchlüsselwort. In .NET ist es trübe, weil es laut ECMA falsch ist, aber gemäß der Laufzeit richtig. In jedem lockFall kümmert sich das definitiv nicht darum.
Aaronaught
Huh? Ich kann nicht sehen, wo Ihre Aussage nicht mit dem übereinstimmt, was ich gesagt habe, und ich habe auch nicht gesagt, dass das Volatile in irgendeiner Weise mit der Sperrsemantik zusammenhängt.
Jason Williams
6
Ihre Antwort behauptet, wie einige andere Aussagen in diesem Thread, dass lockder Code threadsicher ist. Dieser Teil ist wahr, aber das Double-Check-Sperrmuster kann ihn unsicher machen . Das scheint dir zu fehlen. Diese Antwort scheint sich über die Bedeutung und den Zweck eines Double-Check-Schlosses zu schlängeln, ohne jemals die Thread-Sicherheitsprobleme anzusprechen, die der Grund dafür sind volatile.
Aaronaught
1
Wie kann es unsicher machen, wenn instancees mit markiert ist volatile?
UserControl
5

Sie sollten flüchtig mit dem Double-Check-Lock-Muster verwenden.

Die meisten Leute verweisen auf diesen Artikel als Beweis dafür, dass Sie keine flüchtigen Bestandteile benötigen: https://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S10

Sie lesen jedoch nicht bis zum Ende: " Ein letztes Wort der Warnung - Ich vermute nur das x86-Speichermodell aufgrund des beobachteten Verhaltens auf vorhandenen Prozessoren. Daher sind Low-Lock-Techniken auch fragil, da Hardware und Compiler mit der Zeit aggressiver werden können Hier sind einige Strategien, um die Auswirkungen dieser Fragilität auf Ihren Code zu minimieren. Vermeiden Sie nach Möglichkeit Low-Lock-Techniken. (...) Nehmen Sie schließlich das schwächste Speichermodell an, indem Sie flüchtige Deklarationen verwenden, anstatt sich auf implizite Garantien zu verlassen . "

Wenn Sie mehr Überzeugungsarbeit benötigen, lesen Sie diesen Artikel über die ECMA-Spezifikation, die für andere Plattformen verwendet wird: msdn.microsoft.com/en-us/magazine/jj863136.aspx

Wenn Sie weitere Überzeugungsarbeit benötigen, lesen Sie diesen neueren Artikel, in dem Optimierungen vorgenommen werden können, die verhindern, dass er ohne Flüchtigkeit funktioniert: msdn.microsoft.com/en-us/magazine/jj883956.aspx

Zusammenfassend lässt sich sagen, dass es für Sie "möglicherweise" funktioniert, ohne dass es im Moment flüchtig ist, aber es ist nicht wahrscheinlich, dass es den richtigen Code schreibt und entweder die flüchtigen oder die volatileread / write-Methoden verwendet. Artikel, die eine andere Vorgehensweise vorschlagen, lassen manchmal einige der möglichen Risiken von JIT / Compiler-Optimierungen aus, die sich auf Ihren Code auswirken könnten, sowie uns zukünftige Optimierungen, die möglicherweise Ihren Code beschädigen könnten. Wie bereits im letzten Artikel erwähnt, gelten frühere Annahmen, ohne Volatilität zu arbeiten, möglicherweise nicht für ARM.

user2685937
quelle
1
Gute Antwort. Die einzig richtige Antwort auf diese Frage ist einfach "Nein". Demnach ist die akzeptierte Antwort falsch.
Dennis Kassel
3

AFAIK (und - seien Sie vorsichtig, ich mache nicht viele gleichzeitige Sachen) nein. Die Sperre gibt Ihnen nur die Synchronisation zwischen mehreren Konkurrenten (Threads).

volatile hingegen weist Ihre Maschine an, den Wert jedes Mal neu zu bewerten, damit Sie nicht auf einen zwischengespeicherten (und falschen) Wert stoßen.

Siehe http://msdn.microsoft.com/en-us/library/ms998558.aspx und beachten Sie das folgende Zitat:

Außerdem wird die Variable als flüchtig deklariert, um sicherzustellen, dass die Zuweisung zur Instanzvariablen abgeschlossen ist, bevor auf die Instanzvariable zugegriffen werden kann.

Eine Beschreibung von flüchtig: http://msdn.microsoft.com/en-us/library/x13ttww7%28VS.71%29.aspx

Benjamin Podszun
quelle
2
Eine "Sperre" bietet auch eine Speicherbarriere, die gleich (oder besser als) flüchtig ist.
Henk Holterman
2

Ich glaube, ich habe gefunden, wonach ich gesucht habe. Details finden Sie in diesem Artikel - http://msdn.microsoft.com/en-us/magazine/cc163715.aspx#S10 .

Zusammenfassend ist in dieser Situation ein flüchtiger Modifikator in .NET in der Tat nicht erforderlich. In schwächeren Speichermodellen können Schreibvorgänge im Konstruktor eines träge initiierten Objekts jedoch nach dem Schreiben in das Feld verzögert werden, sodass andere Threads möglicherweise eine beschädigte Nicht-Null-Instanz in der ersten if-Anweisung lesen.

Konstantin
quelle
1
Ganz unten in diesem Artikel lesen Sie sorgfältig, insbesondere den letzten Satz, den der Autor sagt: "Ein letztes Wort der Warnung - Ich vermute nur das x86-Speichermodell aufgrund des beobachteten Verhaltens auf vorhandenen Prozessoren. Daher sind Low-Lock-Techniken auch deshalb fragil, weil Hardware und Compiler können mit der Zeit aggressiver werden. Hier sind einige Strategien, um die Auswirkungen dieser Fragilität auf Ihren Code zu minimieren. Vermeiden Sie nach Möglichkeit Low-Lock-Techniken. (...) Nehmen Sie schließlich das schwächste Speichermodell an, das möglich ist. flüchtige Erklärungen verwenden, anstatt sich auf implizite Garantien zu verlassen. "
user2685937
1
Wenn Sie mehr Überzeugungskraft benötigen, lesen Sie diesen Artikel in der ECMA-Spezifikation, der für andere Plattformen verwendet wird: msdn.microsoft.com/en-us/magazine/jj863136.aspx Wenn Sie weitere Überzeugungsarbeit benötigen, lesen Sie diesen neueren Artikel, in dem Optimierungen vorgenommen werden können das verhindert, dass es ohne flüchtige Bestandteile funktioniert: msdn.microsoft.com/en-us/magazine/jj883956.aspx Zusammenfassend lässt sich sagen, dass es für den Moment "möglicherweise" für Sie ohne flüchtige Bestandteile funktioniert, aber es besteht keine Chance, dass es den richtigen Code schreibt und verwendet flüchtig oder die volatileread / write-Methoden.
user2685937
1

Das lockreicht aus. Die MS-Sprachspezifikation (3.0) selbst erwähnt dieses genaue Szenario in §8.12, ohne Folgendes zu erwähnen volatile:

Ein besserer Ansatz besteht darin, den Zugriff auf statische Daten durch Sperren eines privaten statischen Objekts zu synchronisieren. Zum Beispiel:

class Cache
{
    private static object synchronizationObject = new object();
    public static void Add(object x) {
        lock (Cache.synchronizationObject) {
          ...
        }
    }
    public static void Remove(object x) {
        lock (Cache.synchronizationObject) {
          ...
        }
    }
}
Marc Gravell
quelle
Jon Skeet sagt in seinem Artikel ( yoda.arachsys.com/csharp/singleton.html ), dass in diesem Fall flüchtig für eine ordnungsgemäße Speicherbarriere erforderlich ist. Marc, kannst du das kommentieren?
Konstantin
Ah, ich hatte das doppelt überprüfte Schloss nicht bemerkt; einfach: tu das nicht ;-p
Marc Gravell
Ich denke tatsächlich, dass ein doppelt geprüftes Schloss in Bezug auf die Leistung eine gute Sache ist. Auch wenn es notwendig ist, ein Feld flüchtig zu machen, während auf es innerhalb der Sperre zugegriffen wird, ist die doppelte Sperre nicht viel schlimmer als jede andere Sperre ...
Konstantin
Aber ist es so gut wie der separate Klassenansatz, den Jon erwähnt?
Marc Gravell
-3

Dies ist ein ziemlich guter Beitrag über die Verwendung von volatile mit doppelt überprüfter Verriegelung:

http://tech.puredanger.com/2007/06/15/double-checked-locking/

Wenn in Java eine Variable geschützt werden soll, müssen Sie sie nicht sperren, wenn sie als flüchtig markiert ist

Mark Pope
quelle
3
Interessant, aber nicht unbedingt sehr hilfreich. Das Speichermodell der JVM und das Speichermodell der CLR sind nicht dasselbe.
bcat