Java Singleton und Synchronisation

117

Bitte klären Sie meine Fragen zu Singleton und Multithreading:

  • Was ist der beste Weg, um Singleton in Java in einer Multithread-Umgebung zu implementieren?
  • Was passiert, wenn mehrere Threads gleichzeitig versuchen, auf die getInstance() Methode zuzugreifen ?
  • Können wir Singletons machen getInstance() synchronized?
  • Wird bei Verwendung von Singleton-Klassen wirklich eine Synchronisierung benötigt?
RickDavis
quelle

Antworten:

211

Ja, das ist notwendig. Es gibt verschiedene Methoden, mit denen Sie die Thread-Sicherheit mit verzögerter Initialisierung erreichen können:

Drakonische Synchronisation:

private static YourObject instance;

public static synchronized YourObject getInstance() {
    if (instance == null) {
        instance = new YourObject();
    }
    return instance;
}

Diese Lösung erfordert, dass jeder Thread synchronisiert wird, obwohl dies in Wirklichkeit nur die ersten sein müssen.

Überprüfen Sie die Synchronisation :

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

Diese Lösung stellt sicher, dass nur die ersten Threads, die versuchen, Ihren Singleton zu erwerben, den Prozess des Erwerbs der Sperre durchlaufen müssen.

Initialisierung auf Abruf :

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

Diese Lösung nutzt die Garantien des Java-Speichermodells für die Klasseninitialisierung, um die Thread-Sicherheit zu gewährleisten. Jede Klasse kann nur einmal geladen werden und wird nur geladen, wenn sie benötigt wird. Das heißt, das erste Mal getInstancewird aufgerufen, InstanceHoldergeladen und instanceerstellt, und da dies von ClassLoaders gesteuert wird , ist keine zusätzliche Synchronisation erforderlich.

Jeffrey
quelle
23
Warnung - Seien Sie vorsichtig mit der doppelt überprüften Synchronisation. Es funktioniert nicht richtig mit JVMs vor Java 5 aufgrund von "Problemen" mit dem Speichermodell.
Stephen C
3
-1 Draconian synchronizationund Double check synchronizationgetInstance () - Methode muss statisch sein!
Grimmiger
2
@PeterRader Sie nicht brauchen zu sein static, aber es könnte mehr Sinn machen , wenn sie waren. Wie gewünscht geändert.
Jeffrey
4
Es ist nicht garantiert, dass die Implementierung der doppelt überprüften Sperre funktioniert. Es wird tatsächlich in dem Artikel erklärt, den Sie für doppelt geprüftes Sperren zitiert haben. :) Es gibt dort ein Beispiel für die Verwendung von flüchtigen Stoffen, das für 1,5 und höher ordnungsgemäß funktioniert (doppelt geprüftes Sperren ist unter 1,5 einfach gebrochen). Die ebenfalls im Artikel zitierte Initialisierung bei Bedarf wäre wahrscheinlich eine einfachere Lösung in Ihrer Antwort.
Stuckj
2
@MediumOne AFAIK, rwird für die Richtigkeit nicht benötigt. Es ist nur eine Optimierung, um den Zugriff auf das flüchtige Feld zu vermeiden, da dies weitaus teurer ist als der Zugriff auf eine lokale Variable.
Jeffrey
69

Dieses Muster führt eine thread-sichere Lazy-Initialisierung der Instanz ohne explizite Synchronisation durch!

public class MySingleton {

     private static class Loader {
         static final MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return Loader.INSTANCE;
     }
}

Es funktioniert, weil es den Klassenlader verwendet, um die gesamte Synchronisation für Sie kostenlos durchzuführen: Auf die Klasse MySingleton.Loaderwird zuerst innerhalb der getInstance()Methode zugegriffen , sodass die LoaderKlasse geladen wird, wenn getInstance()sie zum ersten Mal aufgerufen wird. Darüber hinaus garantiert der Klassenladeprogramm, dass alle statischen Initialisierungen abgeschlossen sind, bevor Sie Zugriff auf die Klasse erhalten - das gibt Ihnen Thread-Sicherheit.

Es ist wie Magie.

Es ist dem Aufzählungsmuster von Jhurtado eigentlich sehr ähnlich, aber ich finde das Aufzählungsmuster ein Missbrauch des Aufzählungskonzepts (obwohl es funktioniert)

Böhmisch
quelle
11
Die Synchronisierung ist weiterhin vorhanden. Sie wird nur von der JVM und nicht vom Programmierer erzwungen.
Jeffrey
@ Jeffrey Sie haben natürlich Recht - ich habe alles eingegeben (siehe Änderungen)
Bohemian
2
Ich verstehe, dass es für die JVM keinen Unterschied macht. Ich sage nur, dass es für mich einen Unterschied gemacht hat, was selbstdokumentierten Code betrifft. Ich habe noch nie zuvor alle Großbuchstaben in Java ohne das Schlüsselwort "final" (oder enum) gesehen und ein bisschen kognitive Dissonanz erhalten. Für jemanden, der Java in Vollzeit programmiert, würde es wahrscheinlich keinen Unterschied machen, aber wenn Sie Sprachen hin und her springen, ist es hilfreich, explizit zu sein. Das Gleiche gilt für Neulinge. Obwohl ich sicher bin, dass man sich ziemlich schnell an diesen Stil anpassen kann; Alle Kappen sind wahrscheinlich genug. Ich will nicht wählen, ich mochte deinen Beitrag.
Ruby
1
Ausgezeichnete Antwort, obwohl ich keinen Teil davon bekommen habe. Können Sie näher erläutern? "Außerdem garantiert der Klassenladeprogramm, dass alle statischen Initialisierungen abgeschlossen sind, bevor Sie Zugriff auf die Klasse erhalten - das gibt Ihnen Thread-Sicherheit." , wie das bei der Thread-Sicherheit hilft, darüber bin ich etwas verwirrt.
Gaurav Jain
2
@ wz366 eigentlich, obwohl es nicht notwendig ist, stimme ich aus Stilgründen zu (da es effektiv endgültig ist, weil kein anderer Code darauf zugreifen kann) finalsollte hinzugefügt werden. Getan.
Böhmisch
21

Wenn Sie in einer Multithread-Umgebung in Java arbeiten und sicherstellen müssen, dass alle diese Threads auf eine einzelne Instanz einer Klasse zugreifen, können Sie eine Aufzählung verwenden. Dies hat den zusätzlichen Vorteil, dass Sie die Serialisierung leichter handhaben können.

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

und dann lassen Sie einfach Ihre Threads Ihre Instanz wie folgt verwenden:

Singleton.SINGLE.myMethod();
jhurtado
quelle
8

Ja, Sie müssen getInstance()synchronisieren. Wenn dies nicht der Fall ist, kann es vorkommen, dass mehrere Instanzen der Klasse erstellt werden können.

Stellen Sie sich den Fall vor, in dem zwei Threads gleichzeitig aufgerufen getInstance()werden. Stellen Sie sich nun vor, T1 wird kurz nach der instance == nullPrüfung ausgeführt, und dann wird T2 ausgeführt. Zu diesem Zeitpunkt wird die Instanz nicht erstellt oder festgelegt, sodass T2 die Prüfung besteht und die Instanz erstellt. Stellen Sie sich nun vor, die Ausführung wechselt zurück zu T1. Jetzt wird der Singleton erstellt, aber T1 hat die Prüfung bereits durchgeführt! Es wird fortfahren, das Objekt erneut zu erstellen! Das getInstance()Synchronisieren verhindert dieses Problem.

Es gibt einige Möglichkeiten, Singletons threadsicher zu machen, aber die getInstance()Synchronisierung ist wahrscheinlich die einfachste.

Oleksi
quelle
Wird es helfen, indem Code zur Objekterstellung in den synchronisierten Block eingefügt wird, anstatt die gesamte Methodensynchronisation durchzuführen?
RickDavis
@RaoG Nein. Sie möchten sowohl die Prüfung als auch die Erstellung im Synchronisationsblock. Sie müssen diese beiden Vorgänge ohne Unterbrechungen zusammen ausführen, da sonst die oben beschriebene Situation eintreten kann.
Oleksi
7

Enum Singleton

Der einfachste Weg, einen Singleton zu implementieren, der threadsicher ist, ist die Verwendung einer Aufzählung

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Dieser Code funktioniert seit der Einführung von Enum in Java 1.5

Doppelte Verriegelung

Wenn Sie einen „klassischen“ Singleton codieren möchten, der in einer Multithread-Umgebung (ab Java 1.5) funktioniert, sollten Sie diesen verwenden.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

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

Dies ist vor 1.5 nicht threadsicher, da die Implementierung des flüchtigen Schlüsselworts anders war.

Frühes Laden von Singleton (funktioniert bereits vor Java 1.5)

Diese Implementierung instanziiert den Singleton beim Laden der Klasse und bietet Thread-Sicherheit.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}
Dan Moldovan
quelle
2

Sie können auch einen statischen Codeblock verwenden, um die Instanz beim Laden der Klasse zu instanziieren und Probleme mit der Thread-Synchronisierung zu vermeiden.

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}
usha
quelle
@ Vimsha Ein paar andere Dinge. 1. Sie sollten instanceendgültig machen 2. Sie sollten getInstance()statisch machen .
John Vint
Was würden Sie tun, wenn Sie einen Thread im Singleton erstellen möchten?
Arun George
@ arun-george Verwenden Sie einen Thread-Pool, einen einzelnen Thread-Pool, falls erforderlich, und umgeben Sie ihn mit einem while (true) -try-catch-throwable, wenn Sie sicherstellen möchten, dass Ihr Thread niemals stirbt, egal welcher Fehler?
Tgkprog
0

Was ist der beste Weg, um Singleton in Java in einer Multithread-Umgebung zu implementieren?

In diesem Beitrag erfahren Sie, wie Sie Singleton am besten implementieren können.

Was ist ein effizienter Weg, um ein Singleton-Muster in Java zu implementieren?

Was passiert, wenn mehrere Threads gleichzeitig versuchen, auf die Methode getInstance () zuzugreifen?

Dies hängt davon ab, wie Sie die Methode implementiert haben. Wenn Sie die doppelte Sperre ohne flüchtige Variable verwenden, erhalten Sie möglicherweise ein teilweise erstelltes Singleton-Objekt.

Weitere Informationen finden Sie in dieser Frage:

Warum wird in diesem Beispiel der doppelt überprüften Verriegelung flüchtig verwendet?

Können wir getInstance () von Singleton synchronisieren?

Wird bei Verwendung von Singleton-Klassen wirklich eine Synchronisierung benötigt?

Nicht erforderlich, wenn Sie den Singleton wie folgt implementieren

  1. statische Intitalisierung
  2. Aufzählung
  3. LazyInitalaization mit Initialization-on-Demand_holder_idiom

Weitere Informationen finden Sie in dieser Frage

Java Singleton Design Pattern: Fragen

Ravindra Babu
quelle
0
public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

Quelle: Effektives Java -> Punkt 2

Es wird empfohlen, es zu verwenden, wenn Sie sicher sind, dass die Klasse immer Singleton bleibt.

Ashish
quelle