Multithreading in C#, Teil 19: Lazy Initialization

Unter Lazy Initialization versteht man das späte Erzeugen von Objektinstanzen. Das heisst, Instanzen von Objekten werden erst zu dem Zeitpunkt erzeugt zu dem sie benötigt werden. Dies kann die Performance von Anwendungen erhöhen und deren Speicherverbrauch verringern. In Multithreading-Szenarien muss das Erzeugen dieser Objektinstanzen threadsicher erfolgen. In diesem Artikel möchte ich Ihnen daher einige Möglichkeiten zur Implementierung einer Lazy Initialization aufzeigen.

 
Initialisierung in Singlethread Applikationen

Eine einfache Möglichkeit der späten Initialisierung von Instanzen zeigt der nachfolgende Quellcode. Im Getter wird geprüft ob die Objektinstanz bereits erzeugt wurde oder noch erzeugt werden muss.

class Program
{
    private MyClass _myClass;

    static void Main(string[] args)
    {
    }
        
    public MyClass MyClass
    {
        get
        {
            if (_myClass == null) 
            {
                _myClass = new MyClass();
            }

            return _myClass;
        }
    }
}

 
Diese Lösung ist nicht threadsicher und funktioniert somit nur in Singlethread Anwendungen.

 
Initialisierung in Multithread Applikationen

Um das zuvor gezeigte Beispiel threadsicher zu implementieren, kann entsprechend eine Zugriffssperre mittels eines lock Objektes eingefügt werden. Der nachfolgende Quellcode zeigt diese Erweiterung.

class Program
{
    private MyClass _myClass;
    readonly object _lock = new object();

    static void Main(string[] args)
    {
    }

    public MyClass MyClass
    {
        get
        {
            lock (_lock)
            {
                if (_myClass == null)
                {
                    _myClass = new MyClass();
                }

                return _myClass;
            }
        }
    }
}

 
Initialisierung mittels Double Check Locking Pattern

Die threadsicher Initialisierung mittels lock Objekt lässt sich mittels des Double Check Locking Patterns optimieren. Dazu wird das zu initialisierende Objekt als volatil gekennzeichnet und kann nun ausserhalb des lock Objektes geprüft werden. Dies erspart den Aufruf des zeitaufwendigen lock Objektes für den Fall dass das Objekt bereits instanziiert wurde. Der nachfolgende Quellcode zeigt die erweiterte Beispielimplementierung.

class Program
{
    private volatile MyClass _myClass;
    readonly object _lock = new object();

    static void Main(string[] args)
    {
    }

    public MyClass MyClass
    {
        get
        {
            if (_myClass == null) //erster check vor dem lock
            {
                lock (_lock)
                {
                    if (_myClass == null) //zweiter check nach dem lock
                    {
                        _myClass = new MyClass();
                    }
                }
            }

            return _myClass;
        }
    }
}

 
Initialisierung mittels Race-to-Initalize Pattern

Zum Abschluss möchte ich Ihnen ein weiteres Pattern vorstellen, das Race-to-Initialize Pattern. Dieses bietet eine geschwindigkeitsoptimierte Implementierung welche komplett ohne lock Mechanismen auskommt. Es besteht aber die zwingende Voraussetzung, dass der Konstruktor des zu erstellende Objektes threadsicher ist. Werden in diesem Konstruktor beispielsweise Variablen initialisiert, so kann das Pattern nicht eingesetzt werden.

Das Race-to-Initialize Pattern sorgt dafür, dass nur eine Instanz des Objektes zurückgegeben und verwendet wird. Dabei kann es aber wegen der fehlenden lock Mechanismen vorkommen das mehrere Threads gleichzeitig das Objekt anfordern. In diesem Fall werden zwar mehrere Objektinstanzen erstellt, aber das Pattern sorgt dafür dass nur eine Instanz und zwar die zuerst erstellte verwendet wird. Dieses Vorgehen erklärt warum der Objekt-Konstruktor zwingend threadsicher sein muss. Der nachfolgende Quellcode zeigt das Pattern.

class Program
{
    private volatile MyClass _myClass;

    static void Main(string[] args)
    {
    }

    public MyClass MyClass
    {
        get
        {
            if (_myClass == null) 
            {
                var myClassInstance = new MyClass();

                Interlocked.CompareExchange(
                    ref _myClass, 
                    myClassInstance, 
                    null);
            }

            return _myClass;
        }
    }
}

 
Fazit

Die späte Initialisierung einer Instanz ist auch in Multithreading-Szenarien auf sehr einfache Weise möglich. Ich empfehle Ihnen das Double Check Locking Pattern zu verwenden, da dieses threadsicher, schnell und einfach ist.

Werbung
Dieser Beitrag wurde unter .NET, C#, Multithreading abgelegt und mit , , verschlagwortet. Setze ein Lesezeichen auf den Permalink.

Eine Antwort zu Multithreading in C#, Teil 19: Lazy Initialization

  1. Pingback: Multithreading in C#, Teil 20: Lazy und LazyInitializer | coders corner

Kommentar verfassen

Trage deine Daten unten ein oder klicke ein Icon um dich einzuloggen:

WordPress.com-Logo

Du kommentierst mit deinem WordPress.com-Konto. Abmelden /  Ändern )

Facebook-Foto

Du kommentierst mit deinem Facebook-Konto. Abmelden /  Ändern )

Verbinde mit %s