Ich habe einige Fragen zum Singleton-Muster, wie hier dokumentiert: http://msdn.microsoft.com/en-us/library/ff650316.aspx
Der folgende Code ist ein Auszug aus dem Artikel:
using System;
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;
}
}
}
Muss im obigen Beispiel die Instanz vor und nach der Sperre zweimal mit null verglichen werden? Ist das notwendig? Warum nicht zuerst die Sperre durchführen und den Vergleich durchführen?
Gibt es ein Problem bei der Vereinfachung auf Folgendes?
public static Singleton Instance
{
get
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
return instance;
}
}
Ist das Durchführen des Schlosses teuer?
c#
design-patterns
singleton
Wayne Phipps
quelle
quelle
Antworten:
Das Durchführen der Sperre ist im Vergleich zur einfachen Zeigerprüfung furchtbar teuer
instance != null
.Das Muster, das Sie hier sehen, wird als doppelt geprüftes Sperren bezeichnet . Ihr Zweck ist es, die teure Sperroperation zu vermeiden, die nur einmal benötigt wird (wenn der Singleton zum ersten Mal aufgerufen wird). Die Implementierung ist so, weil sie auch sicherstellen muss, dass bei der Initialisierung des Singletons keine Fehler aufgrund von Thread-Race-Bedingungen auftreten.
Stellen Sie sich das so vor: Ein bloßer
null
Scheck (ohne alock
) gibt Ihnen garantiert nur dann eine korrekte verwendbare Antwort, wenn diese Antwort "Ja, das Objekt ist bereits konstruiert" lautet. Wenn die Antwort jedoch "noch nicht erstellt" lautet, haben Sie nicht genügend Informationen, da Sie eigentlich wissen wollten, dass sie "noch nicht erstellt wurde und kein anderer Thread beabsichtigt, sie in Kürze zu erstellen ". Sie verwenden also die äußere Prüfung als sehr schnellen ersten Test und leiten das richtige, fehlerfreie, aber "teure" Verfahren (Sperren und Prüfen) nur dann ein, wenn die Antwort "Nein" lautet.Die obige Implementierung ist für die meisten Fälle gut genug, aber an dieser Stelle ist es eine gute Idee, Jon Skeets Artikel über Singletons in C # zu lesen, in dem auch andere Alternativen bewertet werden.
quelle
Lazy<T>
macht das diesen Job einfach perfekt.Die
Lazy<T>
Version:public sealed class Singleton { private static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton()); public static Singleton Instance => lazy.Value; private Singleton() { } }
Benötigt .NET 4 und C # 6.0 (VS2015) oder neuer.
quelle
Sperren durchführen: Ziemlich billig (immer noch teurer als ein Nulltest).
Durchführen einer Sperre, wenn ein anderer Thread sie hat: Sie erhalten die Kosten für alles, was sie beim Sperren noch tun müssen, zusätzlich zu Ihrer eigenen Zeit.
Durchführen einer Sperre, wenn ein anderer Thread sie hat und Dutzende anderer Threads ebenfalls darauf warten: Verkrüppeln.
Aus Leistungsgründen möchten Sie immer Sperren haben, die ein anderer Thread wünscht, und zwar für den kürzestmöglichen Zeitraum.
Natürlich ist es einfacher, über "breite" Schlösser nachzudenken als über schmale, daher lohnt es sich, mit ihnen zu beginnen und sie nach Bedarf zu optimieren, aber es gibt einige Fälle, die wir aus Erfahrung und Vertrautheit lernen, in denen ein schmalerer zum Muster passt.
(Übrigens, wenn Sie möglicherweise nur
private static volatile Singleton instance = new Singleton()
Singletons verwenden können oder wenn Sie möglicherweise einfach keine Singletons verwenden können, sondern stattdessen eine statische Klasse verwenden, sind beide in Bezug auf diese Bedenken besser).quelle
Der Grund ist die Leistung. Wenn
instance != null
(was bis auf das allererste Mal immer der Fall sein wird), ist dies nicht kostspieliglock
: Zwei Threads, die gleichzeitig auf den initialisierten Singleton zugreifen, würden unnötig synchronisiert.quelle
In fast allen Fällen (dh in allen Fällen außer den allerersten)
instance
wird nicht null sein. Das Erwerben einer Sperre ist teurer als eine einfache Überprüfung.instance
Daher ist es eine schöne und kostenlose Optimierung, den Wert vor dem Sperren einmal zu überprüfen .Dieses Muster wird als doppelt geprüftes Sperren bezeichnet: http://en.wikipedia.org/wiki/Double-checked_locking
quelle
Jeffrey Richter empfiehlt Folgendes:
public sealed class Singleton { private static readonly Object s_lock = new Object(); private static Singleton instance = null; private Singleton() { } public static Singleton Instance { get { if(instance != null) return instance; Monitor.Enter(s_lock); Singleton temp = new Singleton(); Interlocked.Exchange(ref instance, temp); Monitor.Exit(s_lock); return instance; } } }
quelle
Sie können eifrig eine thread-sichere Singleton-Instanz erstellen, abhängig von Ihren Anwendungsanforderungen. Dies ist prägnanter Code, obwohl ich die faule Version von @ andasa bevorzugen würde.
public sealed class Singleton { private static readonly Singleton instance = new Singleton(); private Singleton() { } public static Singleton Instance() { return instance; } }
quelle
Dies wird als doppelt überprüfter Sperrmechanismus bezeichnet. Zuerst prüfen wir, ob die Instanz erstellt wurde oder nicht. Wenn nicht, synchronisieren wir nur die Methode und erstellen die Instanz. Dadurch wird die Leistung der Anwendung drastisch verbessert. Das Durchführen der Sperre ist schwer. Um die Sperre zu vermeiden, müssen wir zuerst den Nullwert überprüfen. Dies ist auch threadsicher und der beste Weg, um die beste Leistung zu erzielen. Bitte schauen Sie sich den folgenden Code an.
public sealed class Singleton { private static readonly object Instancelock = new object(); private Singleton() { } private static Singleton instance = null; public static Singleton GetInstance { get { if (instance == null) { lock (Instancelock) { if (instance == null) { instance = new Singleton(); } } } return instance; } } }
quelle
Eine andere Version von Singleton, bei der die folgende Codezeile die Singleton-Instanz zum Zeitpunkt des Anwendungsstarts erstellt.
private static readonly Singleton singleInstance = new Singleton();
Hier kümmert sich CLR (Common Language Runtime) um die Objektinitialisierung und die Thread-Sicherheit. Das bedeutet, dass wir keinen Code explizit schreiben müssen, um die Thread-Sicherheit für eine Multithread-Umgebung zu gewährleisten.
public sealed class Singleton { private static int counter = 0; private Singleton() { counter++; Console.WriteLine("Counter Value " + counter.ToString()); } private static readonly Singleton singleInstance = new Singleton(); public static Singleton GetInstance { get { return singleInstance; } } public void PrintDetails(string message) { Console.WriteLine(message); } }
von main:
static void Main(string[] args) { Parallel.Invoke( () => PrintTeacherDetails(), () => PrintStudentdetails() ); Console.ReadLine(); } private static void PrintTeacherDetails() { Singleton fromTeacher = Singleton.GetInstance; fromTeacher.PrintDetails("From Teacher"); } private static void PrintStudentdetails() { Singleton fromStudent = Singleton.GetInstance; fromStudent.PrintDetails("From Student"); }
quelle