3.1 Aktives Objekt

Ein aktives Objekt ist ein Entwurfsmuster, das den Ausführungsthread einer Methode von dem Thread trennt, in dem sie aufgerufen wurde. Der Zweck dieses Musters besteht darin, eine parallele Ausführung mithilfe asynchroner Methodenaufrufe und eines Anforderungsverarbeitungsplaners bereitzustellen.

Vereinfachte Version:

aktives Objekt

Klassische Variante:

Aktives Objekt 2

Diese Vorlage besteht aus sechs Elementen:

  • Ein Proxy-Objekt, das eine Schnittstelle zu den öffentlichen Methoden des Clients bereitstellt.
  • Eine Schnittstelle, die Zugriffsmethoden für das aktive Objekt definiert.
  • Liste der eingehenden Anfragen von Kunden.
  • Ein Planer, der die Reihenfolge bestimmt, in der Abfragen ausgeführt werden sollen.
  • Implementierung aktiver Objektmethoden.
  • Eine Rückrufprozedur oder eine Variable, damit der Client ein Ergebnis erhält.

3.2 Schloss

Das Sperrmuster ist ein Synchronisierungsmechanismus, der den exklusiven Zugriff auf eine gemeinsam genutzte Ressource zwischen mehreren Threads ermöglicht. Sperren sind eine Möglichkeit, Richtlinien zur Parallelitätskontrolle durchzusetzen.

Grundsätzlich wird eine Soft-Sperre verwendet, wobei davon ausgegangen wird, dass jeder Thread versucht, „die Sperre zu erlangen“, bevor er auf die entsprechende gemeinsam genutzte Ressource zugreift.

Einige Systeme bieten jedoch einen obligatorischen Sperrmechanismus, mit dem ein unbefugter Zugriffsversuch auf eine gesperrte Ressource abgebrochen wird, indem eine Ausnahme für den Thread ausgelöst wird, der versucht hat, Zugriff zu erhalten.

Ein Semaphor ist die einfachste Art von Schloss. Beim Datenzugriff wird nicht zwischen Zugriffsmodi unterschieden: geteilt (nur Lesen) oder exklusiv (Lesen/Schreiben). Im Shared-Modus können mehrere Threads eine Sperre anfordern, um im schreibgeschützten Modus auf Daten zuzugreifen. Der exklusive Zugriffsmodus wird auch in den Aktualisierungs- und Löschalgorithmen verwendet.

Sperrmuster

Die Arten von Sperren unterscheiden sich durch die Strategie, die Fortsetzung der Ausführung des Threads zu blockieren. In den meisten Implementierungen verhindert eine Anforderung einer Sperre, dass der Thread weiter ausgeführt wird, bis die gesperrte Ressource verfügbar ist.

Ein Spinlock ist eine Sperre, die in einer Schleife wartet, bis der Zugriff gewährt wird. Eine solche Sperre ist sehr effizient, wenn ein Thread kurze Zeit auf eine Sperre wartet, wodurch eine übermäßige Neuplanung von Threads vermieden wird. Die Kosten für das Warten auf den Zugriff sind erheblich, wenn einer der Threads die Sperre über einen längeren Zeitraum hält.

Sperrmuster 2

Um den Verriegelungsmechanismus effektiv zu implementieren, ist Unterstützung auf Hardwareebene erforderlich. Hardwareunterstützung kann als eine oder mehrere atomare Operationen wie „Test-and-Set“, „Fetch-and-Add“ oder „Compare-and-Swap“ implementiert werden. Mit solchen Anweisungen können Sie ohne Unterbrechung überprüfen, ob die Sperre frei ist, und wenn ja, dann die Sperre erwerben.

3.3 Überwachen

Das Monitor-Muster ist ein Prozessinteraktions- und Synchronisierungsmechanismus auf hoher Ebene, der Zugriff auf gemeinsam genutzte Ressourcen ermöglicht. Ein Ansatz zur Synchronisierung von zwei oder mehr Computeraufgaben mithilfe einer gemeinsamen Ressource, normalerweise Hardware oder einer Reihe von Variablen.

Beim Monitor-basierten Multitasking fügt der Compiler oder Interpreter für den Programmierer transparent Lock-Unlock-Code in entsprechend formatierte Routinen ein und erspart ihm so den expliziten Aufruf von Synchronisierungsprimitiven.

Der Monitor besteht aus:

  • eine Reihe von Prozeduren, die mit einer gemeinsam genutzten Ressource interagieren
  • Mutex
  • Variablen, die dieser Ressource zugeordnet sind
  • eine Invariante, die Bedingungen definiert, um eine Race-Bedingung zu vermeiden

Die Monitorprozedur erfasst den Mutex, bevor sie mit der Arbeit beginnt, und hält ihn entweder, bis die Prozedur beendet wird oder bis auf eine bestimmte Bedingung gewartet wird. Wenn jede Prozedur vor der Freigabe des Mutex garantiert, dass die Invariante wahr ist, kann keine Aufgabe die Ressource in einer Race-Bedingung erwerben.

So funktioniert der synchronisierte Operator in Java mit den Methoden wait()und notify().

3.4 Überprüfen Sie die Verriegelung noch einmal

Bei der doppelt geprüften Sperre handelt es sich um ein paralleles Entwurfsmuster, das den Aufwand für die Erlangung einer Sperre reduzieren soll.

Zunächst wird die Sperrbedingung ohne Synchronisierung überprüft. Ein Thread versucht nur dann, eine Sperre zu erhalten, wenn das Ergebnis der Prüfung darauf hinweist, dass er die Sperre erwerben muss.

//Double-Checked Locking
public final class Singleton {
private static Singleton instance; //Don't forget volatile modifier

public static Singleton getInstance() {
     if (instance == null) {                //Read

         synchronized (Singleton.class) {    //
             if (instance == null) {         //Read Write
                 instance = new Singleton(); //
             }
         }
     }
 }

Wie erstelle ich ein Singleton-Objekt in einer Thread-sicheren Umgebung?

public static Singleton getInstance() {
   if (instance == null)
    instance = new Singleton();
}

Wenn Sie ein Singleton-Objekt aus verschiedenen Threads erstellen, kann es vorkommen, dass mehrere Objekte gleichzeitig erstellt werden. Dies ist nicht akzeptabel. Daher ist es sinnvoll, die Objekterstellung in eine synchronisierte Anweisung zu packen.

public static Singleton getInstance() {
    synchronized (Singleton.class) {
        if (instance == null)
        instance = new Singleton();
    }
}

Dieser Ansatz wird funktionieren, hat aber einen kleinen Nachteil. Nachdem das Objekt erstellt wurde, wird bei jedem zukünftigen Versuch, es abzurufen, eine Überprüfung im synchronisierten Block durchgeführt, was bedeutet, dass der aktuelle Thread und alles, was damit verbunden ist, gesperrt wird. Daher kann dieser Code etwas optimiert werden:

public static Singleton getInstance() {
     if (instance != null)
        return instance;

    synchronized (Singleton.class) {
        if (instance == null)
        instance = new Singleton();
    }
}

Auf einigen Sprachen und/oder auf einigen Maschinen ist es nicht möglich, dieses Muster sicher zu implementieren. Daher wird es manchmal als Anti-Pattern bezeichnet. Solche Merkmale haben zum Auftreten der strengen Ordnungsbeziehung „passiert vorher“ im Java-Speichermodell und im C++-Speichermodell geführt.

Es wird normalerweise verwendet, um den Aufwand für die Implementierung einer verzögerten Initialisierung in Multithread-Programmen wie dem Singleton-Entwurfsmuster zu reduzieren. Bei der verzögerten Initialisierung einer Variablen wird die Initialisierung verzögert, bis der Wert der Variablen für die Berechnung benötigt wird.

3.5 Planer

Der Scheduler ist ein paralleles Entwurfsmuster, das einen Mechanismus zum Implementieren einer Planungsrichtlinie bereitstellt, jedoch unabhängig von einer bestimmten Richtlinie ist. Steuert die Reihenfolge, in der Threads sequenziellen Code ausführen sollen, mithilfe eines Objekts, das explizit die Reihenfolge der wartenden Threads angibt.