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


812

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


1
"Was ist ein effizienter Weg, um ein Singleton-Muster in Java zu implementieren?" Bitte definieren Sie effizient.
Marian Paździoch

medium.com/@kevalpatel2106/… . Dies ist der vollständige Artikel zum Erreichen der Thread-, Reflexions- und Serialisierungssicherheit im Singleton-Muster. Dies ist die gute Quelle, um die Vor- und Nachteile der Singleton-Klasse zu verstehen.
Keval Patel

Wie Joshua Bloch in Effective Java hervorhebt, ist Enum Singleton der beste Weg. Hier habe ich die verschiedenen Implementierungen als faul / eifrig usw.
kategorisiert

Antworten:


782

Verwenden Sie eine Aufzählung:

public enum Foo {
    INSTANCE;
}

Joshua Bloch erläuterte diesen Ansatz in seinem Vortrag " Effective Java Reloaded" bei Google I / O 2008: Link zum Video . Siehe auch Folien 30-32 seiner Präsentation ( effektiv_java_reloaded.pdf ):

Der richtige Weg, um einen serialisierbaren Singleton zu implementieren

public enum Elvis {
    INSTANCE;
    private final String[] favoriteSongs =
        { "Hound Dog", "Heartbreak Hotel" };
    public void printFavorites() {
        System.out.println(Arrays.toString(favoriteSongs));
    }
}

Bearbeiten: Ein Online-Teil von "Effective Java" sagt:

"Dieser Ansatz ist funktional gleichbedeutend mit dem Public-Field-Ansatz, mit der Ausnahme, dass er präziser ist, die Serialisierungsmaschinerie kostenlos zur Verfügung stellt und eine Garantie gegen mehrfache Instanziierung bietet, selbst angesichts ausgefeilter Serialisierungs- oder Reflexionsangriffe noch weithin angenommen wird, ein Einzelelement - enum - Typ ist der beste Weg , um einen Singleton zu implementieren .“


203
Ich denke, die Leute sollten Enums nur als eine Klasse mit einer Funktion betrachten. Wenn Sie die Instanzen Ihrer Klasse zur Kompilierungszeit auflisten können, verwenden Sie eine Aufzählung.
Amir Arad

7
Ich persönlich finde es nicht oft notwendig, das Singleton-Muster direkt zu verwenden. Ich verwende manchmal die Abhängigkeitsinjektion von Spring mit einem Anwendungskontext, der sogenannte Singletons enthält. Meine Dienstprogrammklassen enthalten normalerweise nur statische Methoden, und ich benötige keine Instanzen davon.
Stephen Denne

3
Hallo, kann mir jemand sagen, wie diese Art von Singleton in Testfällen verspottet und getestet werden kann. Ich habe versucht, eine gefälschte Singleton-Instanz gegen diesen Typ auszutauschen, konnte dies aber nicht.
Ashish Sharma

29
Ich denke, es macht Sinn, aber ich mag es immer noch nicht. Wie würden Sie einen Singleton erstellen, der eine andere Klasse erweitert? Wenn Sie eine Aufzählung verwenden, können Sie nicht.
Chharvey

11
@bvdb: Wenn Sie viel Flexibilität wünschen, haben Sie es bereits vermasselt, indem Sie überhaupt einen Singleton implementiert haben. Die Möglichkeit, bei Bedarf eine unabhängige Instanz zu erstellen, ist an sich schon von unschätzbarem Wert.
CHao

233

Je nach Verwendung gibt es mehrere "richtige" Antworten.

Da Java5 der beste Weg ist, dies zu tun, ist die Verwendung einer Aufzählung:

public enum Foo {
   INSTANCE;
}

Vor Java5 ist der einfachste Fall:

public final class Foo {

    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

Lassen Sie uns den Code durchgehen. Zunächst möchten Sie, dass die Klasse endgültig ist. In diesem Fall habe ich das finalSchlüsselwort verwendet, um die Benutzer darüber zu informieren, dass es endgültig ist. Dann müssen Sie den Konstruktor privat machen, um zu verhindern, dass Benutzer ihr eigenes Foo erstellen. Das Auslösen einer Ausnahme vom Konstruktor verhindert, dass Benutzer Reflektion verwenden, um ein zweites Foo zu erstellen. Anschließend erstellen Sie ein private static final FooFeld für die einzige Instanz und eine public static Foo getInstance()Methode für die Rückgabe. Die Java-Spezifikation stellt sicher, dass der Konstruktor nur aufgerufen wird, wenn die Klasse zum ersten Mal verwendet wird.

Wenn Sie ein sehr großes Objekt oder einen schweren Konstruktionscode haben UND auch über andere zugängliche statische Methoden oder Felder verfügen, die möglicherweise verwendet werden, bevor eine Instanz benötigt wird, müssen Sie nur dann eine verzögerte Initialisierung verwenden.

Sie können a verwenden private static class, um die Instanz zu laden. Der Code würde dann so aussehen:

public final class Foo {

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }
}

Da die Zeile private static final Foo INSTANCE = new Foo();nur ausgeführt wird, wenn die Klasse FooLoader tatsächlich verwendet wird, wird die verzögerte Instanziierung erledigt und es wird garantiert, dass sie threadsicher ist.

Wenn Sie Ihr Objekt auch serialisieren möchten, müssen Sie sicherstellen, dass durch die Deserialisierung keine Kopie erstellt wird.

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static class FooLoader {
        private static final Foo INSTANCE = new Foo();
    }

    private Foo() {
        if (FooLoader.INSTANCE != null) {
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

Die Methode stellt readResolve()sicher, dass die einzige Instanz zurückgegeben wird, auch wenn das Objekt in einem früheren Programmlauf serialisiert wurde.


32
Die Überprüfung auf Reflexion ist nutzlos. Wenn anderer Code die Reflexion von Privaten verwendet, ist das Spiel vorbei. Es gibt keinen Grund, bei einem solchen Missbrauch überhaupt zu versuchen, richtig zu funktionieren. Und wenn Sie es versuchen, wird es sowieso ein unvollständiger "Schutz" sein, nur eine Menge verschwendeten Codes.
Wouter Coekaerts

5
> "Zuerst soll die Klasse endgültig sein". Könnte jemand bitte näher darauf eingehen?
PlagueHammer

2
Der Deserialisierungsschutz ist vollständig aufgehoben (ich denke, dies wird in Effective Java 2nd Ed erwähnt).
Tom Hawtin - Tackline

9
-1 Dies ist absolut nicht der einfachste Fall, es ist erfunden und unnötig komplex. Schauen Sie sich Jonathans Antwort an, um die einfachste Lösung zu finden, die in 99,9% aller Fälle ausreicht.
Michael Borgwardt

2
Dies ist nützlich, wenn Ihr Singleton von einer Oberklasse erben muss. In diesem Fall können Sie das Enum-Singleton-Muster nicht verwenden, da Enums keine Oberklasse haben können (sie können jedoch Schnittstellen implementieren). Beispielsweise verwendet Google Guava ein statisches Endfeld,
Etienne Neveu

139

Haftungsausschluss: Ich habe gerade alle fantastischen Antworten zusammengefasst und in meinen Worten geschrieben.


Während der Implementierung von Singleton haben wir zwei Möglichkeiten:
1. Faules Laden
2. Frühes Laden

Das verzögerte Laden erhöht den Overhead (viele, um ehrlich zu sein). Verwenden Sie ihn daher nur, wenn Sie ein sehr großes Objekt oder einen schweren Konstruktionscode haben UND auch über andere zugängliche statische Methoden oder Felder verfügen, die möglicherweise verwendet werden, bevor eine Instanz benötigt wird Sie müssen die verzögerte Initialisierung verwenden. Andernfalls ist die Auswahl des frühen Ladens eine gute Wahl.

Die einfachste Art, Singleton zu implementieren, ist

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        return INSTANCE;
    }
}

Alles ist gut, bis auf den früh geladenen Singleton. Versuchen wir es mit einem faul geladenen Singleton

class Foo {

    // Our now_null_but_going_to_be sole hero 
    private static Foo INSTANCE = null;

    private Foo() {
        if (INSTANCE != null) {
            // SHOUT  
            throw new IllegalStateException("Already instantiated");
        }
    }

    public static Foo getInstance() {
        // Creating only  when required.
        if (INSTANCE == null) {
            INSTANCE = new Foo();
        }
        return INSTANCE;
    }
}

So weit so gut, aber unser Held wird nicht überleben, wenn er alleine mit mehreren bösen Fäden kämpft, die viele, viele Instanzen unseres Helden wollen. Schützen wir es also vor bösem Multithreading

class Foo {

    private static Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        // No more tension of threads
        synchronized (Foo.class) {
            if (INSTANCE == null) {
                INSTANCE = new Foo();
            }
        }
        return INSTANCE;
    }
}

aber es ist nicht genug, um den Helden zu beschützen, wirklich !!! Dies ist das Beste, was wir tun können / sollten, um unserem Helden zu helfen

class Foo {

    // Pay attention to volatile
    private static volatile Foo INSTANCE = null;

    // TODO Add private shouting constructor

    public static Foo getInstance() {
        if (INSTANCE == null) { // Check 1
            synchronized (Foo.class) {
                if (INSTANCE == null) { // Check 2
                    INSTANCE = new Foo();
                }
            }
        }
        return INSTANCE;
    }
}

Dies wird als "Double-Checked Locking Idiom" bezeichnet. Es ist leicht, die volatile Aussage zu vergessen und schwer zu verstehen, warum es notwendig ist.
Für Details: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Jetzt sind wir uns über böse Fäden sicher, aber was ist mit der grausamen Serialisierung? Wir müssen sicherstellen, dass auch während der Serialisierung kein neues Objekt erstellt wird

class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    private static volatile Foo INSTANCE = null;

    // Rest of the things are same as above

    // No more fear of serialization
    @SuppressWarnings("unused")
    private Object readResolve() {
        return INSTANCE;
    }
}

Die Methode stellt readResolve()sicher, dass die einzige Instanz zurückgegeben wird, auch wenn das Objekt in einem früheren Programmlauf serialisiert wurde.

Schließlich haben wir genügend Schutz gegen Threads und Serialisierung hinzugefügt, aber unser Code sieht sperrig und hässlich aus. Geben wir unserem Helden ein neues Gesicht

public final class Foo implements Serializable {

    private static final long serialVersionUID = 1L;

    // Wrapped in a inner static class so that loaded only when required
    private static class FooLoader {

        // And no more fear of threads
        private static final Foo INSTANCE = new Foo();
    }

    // TODO add private shouting construcor

    public static Foo getInstance() {
        return FooLoader.INSTANCE;
    }

    // Damn you serialization
    @SuppressWarnings("unused")
    private Foo readResolve() {
        return FooLoader.INSTANCE;
    }
}

Ja, das ist unser gleicher Held :)
Da die Zeile private static final Foo INSTANCE = new Foo();nur ausgeführt wird, wenn die Klasse FooLoadertatsächlich verwendet wird, wird die faule Instanziierung erledigt.

und ist es garantiert threadsicher.

Und wir sind so weit gekommen, hier ist der beste Weg, um alles zu erreichen, was wir getan haben, der bestmögliche Weg

 public enum Foo {
       INSTANCE;
   }

Welche intern behandelt werden

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

Das ist es! Keine Angst mehr vor Serialisierung, Threads und hässlichem Code. Auch ENUMS-Singleton werden träge initialisiert .

Dieser Ansatz ist funktional äquivalent zum Public-Field-Ansatz, mit der Ausnahme, dass er präziser ist, die Serialisierungsmaschinerie kostenlos bereitstellt und eine Garantie gegen mehrfache Instanziierung bietet, selbst angesichts ausgefeilter Serialisierungs- oder Reflexionsangriffe. Während dieser Ansatz noch nicht weit verbreitet ist, ist ein Aufzählungstyp mit einem Element der beste Weg, um einen Singleton zu implementieren.

-Joshua Bloch in "Effektives Java"

Jetzt haben Sie vielleicht erkannt, warum ENUMS als der beste Weg zur Implementierung von Singleton angesehen werden, und vielen Dank für Ihre Geduld :)
Aktualisiert in meinem Blog .


3
Nur eine Klarstellung: Singletons, die mit enum implementiert wurden, werden träge initialisiert. Details hier: stackoverflow.com/questions/16771373/…

2
gute Antwort. Eine letzte Sache, überschreiben Sie die Klonmethode, um eine Ausnahme auszulösen.
Riship89

2
@xyz nette Erklärungen, ich habe es sehr genossen und sehr leicht gelernt und ich hoffe, dass ich das nie vergessen habe
Premraj

1
Eine der besten Antworten, die ich je auf Stackoverflow erhalten habe. Vielen Dank!
Shay Tsadok

1
Es gibt ein Serialisierungsproblem bei der Verwendung von Aufzählungen als Singleton: Alle Mitgliedsfeldwerte werden nicht serialisiert und daher nicht wiederhergestellt. Siehe Java Object Serialization Specification, Version 6.0 . Ein weiteres Problem: Keine Versionierung - alle Aufzählungstypen haben eine feste serialVersionUIDvon 0L. Drittes Problem: Keine Anpassung: Alle klassenspezifischen Methoden writeObject, readObject, readObjectNoData, writeReplace und readResolve, die durch Aufzählungstypen definiert sind, werden während der Serialisierung und Deserialisierung ignoriert.
Basil Bourque

124

Die von Stu Thompson veröffentlichte Lösung ist in Java5.0 und höher gültig. Aber ich würde es vorziehen, es nicht zu verwenden, weil ich denke, dass es fehleranfällig ist.

Es ist leicht, die volatile Aussage zu vergessen und schwer zu verstehen, warum es notwendig ist. Ohne das flüchtige Material wäre dieser Code aufgrund des doppelt überprüften Antipatterns nicht mehr threadsicher. Weitere Informationen hierzu finden Sie in Abschnitt 16.2.4 von Java Concurrency in Practice . Kurz gesagt: Dieses Muster (vor Java5.0 oder ohne die flüchtige Anweisung) könnte einen Verweis auf das Bar-Objekt zurückgeben, das sich (noch) in einem falschen Zustand befindet.

Dieses Muster wurde zur Leistungsoptimierung erfunden. Aber das ist wirklich kein wirkliches Problem mehr. Der folgende verzögerte Initialisierungscode ist schnell und - was noch wichtiger ist - einfacher zu lesen.

class Bar {
    private static class BarHolder {
        public static Bar bar = new Bar();
    }

    public static Bar getBar() {
        return BarHolder.bar;
    }
}

1
Meinetwegen! Ich fühle mich einfach wohl mit flüchtigen und es ist Verwendung. Oh, und drei Beifall für JCiP.
Stu Thompson

1
Oh, dies ist anscheinend der Ansatz von William Pugh von FindBugz.
Stu Thompson

4
@Stu Die erste Ausgabe von Effective Java (Copyright 2001) beschreibt dieses Muster unter Punkt 48.
Pascal Thivent

9
@Bno: Was ist mit dem privaten Konstruktor?
XYZ

2
@ AlikElzin-kilaka Nicht ganz. Die Instanz wird in der Klassenladephase für BarHolder erstellt , die bis zum ersten Mal verzögert wird. Der Konstruktor von Bar kann so kompliziert sein, wie Sie möchten, wird jedoch erst beim ersten Mal aufgerufen getBar(). (Und wenn getBares "zu früh" genannt wird, werden Sie das gleiche Problem haben, egal wie Singleons implementiert sind.) Sie können das Laden des Codes in der faulen Klasse hier oben sehen: pastebin.com/iq2eayiR
Ti Strga

95

Thread sicher in Java 5+:

class Foo {
    private static volatile Bar bar = null;
    public static Bar getBar() {
        if (bar == null) {
            synchronized(Foo.class) {
                if (bar == null)
                    bar = new Bar(); 
            }
        }
        return bar;
    }
}

EDIT : volatileAchten Sie hier auf den Modifikator. :) Es ist wichtig, da andere Threads ohne sie vom JMM (Java Memory Model) nicht garantiert werden, um Änderungen an ihrem Wert zu sehen. Die Synchronisation kümmert sich nicht darum - sie serialisiert nur den Zugriff auf diesen Codeblock.

EDIT 2 : Die Antwort von @Bno beschreibt den von Bill Pugh (FindBugs) empfohlenen Ansatz und ist wohl besser. Lesen Sie und stimmen Sie auch über seine Antwort ab.


1
Wo kann ich mehr über den flüchtigen Modifikator erfahren?
elf81


2
Ich denke, es ist wichtig, über Reflexionsangriffe zu sprechen. Zwar müssen sich die meisten Entwickler keine Sorgen machen, aber es scheint, dass Beispiele wie diese (über Enum-basierte Singletons) entweder Code enthalten sollten, der vor Angriffen mit mehreren Instanziierungen schützt, oder einfach einen Haftungsausschluss einfügen, der auf solche Möglichkeiten hinweist.
Luis.espinal

2
Ein flüchtiges Schlüsselwort wird hier nicht benötigt, da durch die Synchronisierung sowohl gegenseitiger Ausschluss als auch Speichersichtbarkeit gewährleistet sind.
Hemant

2
Warum sich mit all dem in Java 5+ beschäftigen? Mein Verständnis ist, dass der Enum-Ansatz sowohl Thread-Sicherheit als auch verzögerte Initialisierung bietet. Es ist auch viel einfacher ... Wenn Sie eine Aufzählung vermeiden möchten, würde ich trotzdem den Ansatz der verschachtelten statischen Klasse verhindern ...
Alexandros

91

Vergessen Sie die verzögerte Initialisierung , es ist zu problematisch. Dies ist die einfachste Lösung:

public class A {    

    private static final A INSTANCE = new A();

    private A() {}

    public static A getInstance() {
        return INSTANCE;
    }
}

21
Singleton-Instanzvariable kann auch endgültig gemacht werden. zB privates statisches Finale A Singleton = neues A ();
Jatanp

19
Dies ist effektiv eine verzögerte Initialisierung, da der statische Singleton erst instanziiert wird, wenn die Klasse geladen wird, und die Klasse erst geladen wird, wenn sie benötigt wird (was ungefähr zu dem Zeitpunkt richtig ist, zu dem Sie zum ersten Mal auf die Methode getInstance () verweisen).
Dan Dyer

7
Wenn Klasse A geladen wird, bevor die Statik instanziiert werden soll, können Sie die Statik in eine statische innere Klasse einschließen, um die Klasseninitialisierung zu entkoppeln.
Tom Hawtin - Tackline

3
Ich bin damit einverstanden, dass diese Antwort die einfachste ist, und Anirudhan, es besteht keine Notwendigkeit, die Instanz für endgültig zu erklären. Kein anderer Thread erhält Zugriff auf die Klasse, während die statischen Elemente initialisiert werden. Dies wird vom Compiler garantiert, dh die gesamte statische Initialisierung erfolgt synchronisiert - nur ein Thread.
Inor

4
Dieser Ansatz hat eine Einschränkung: Der Konstruktor kann keine Ausnahme auslösen.
Wangf

47

Stellen Sie sicher, dass Sie es wirklich brauchen. Führen Sie eine Google-Suche nach "Singleton Anti-Pattern" durch, um einige Argumente dagegen zu sehen. Ich nehme an, daran ist an sich nichts auszusetzen, aber es ist nur ein Mechanismus zum Offenlegen einiger globaler Ressourcen / Daten. Stellen Sie also sicher, dass dies der beste Weg ist. Insbesondere habe ich die Abhängigkeitsinjektion als nützlicher empfunden, insbesondere wenn Sie auch Komponententests verwenden, da DI es Ihnen ermöglicht, verspottete Ressourcen zu Testzwecken zu verwenden.


Sie können auch Scheinwerte mit der traditionellen Methode einfügen, aber ich denke, es ist nicht die Standard- / Srping-Methode, so dass es zusätzliche Arbeit mit nur Gewinn als Legacy-Code ist ...
tgkprog

21

Ich bin verwirrt über einige der Antworten, die DI als Alternative zur Verwendung von Singletons vorschlagen. Dies sind nicht verwandte Konzepte. Sie können DI verwenden, um entweder Singleton- oder Nicht-Singleton-Instanzen (z. B. pro Thread) zu injizieren. Zumindest ist dies der Fall, wenn Sie Spring 2.x verwenden. Ich kann nicht für andere DI-Frameworks sprechen.

Meine Antwort auf das OP wäre also (mit Ausnahme des trivialsten Beispielcodes):

  1. Verwenden Sie dann ein DI-Framework wie Spring
  2. Machen Sie es zu einem Teil Ihrer DI-Konfiguration, ob Ihre Abhängigkeiten Singletons, Anforderungsbereiche, Sitzungsbereiche oder was auch immer sind.

Dieser Ansatz bietet Ihnen eine schöne entkoppelte (und daher flexible und testbare) Architektur, bei der die Verwendung eines Singletons ein leicht umkehrbares Implementierungsdetail ist (vorausgesetzt, alle von Ihnen verwendeten Singletons sind natürlich threadsicher).


4
Vielleicht, weil die Leute nicht mit dir übereinstimmen. Ich habe dich nicht herabgestimmt, aber ich bin anderer Meinung: Ich denke, DI kann verwendet werden, um die gleichen Probleme zu lösen, die Singletons sind. Dies basiert auf dem Verständnis von "Singleton" als "Objekt mit einer einzelnen Instanz, auf das direkt über einen globalen Namen zugegriffen wird" und nicht nur als "Objekt mit einer einzelnen Instanz", was möglicherweise etwas schwierig ist.
Tom Anderson

2
Um dies etwas zu erweitern, betrachten Sie eine TicketNumberer, die eine einzelne globale Instanz haben muss und in der Sie eine Klasse schreiben möchten, die eine TicketIssuerCodezeile enthält int ticketNumber = ticketNumberer.nextTicketNumber();. Im traditionellen Singleton-Denken müsste die vorherige Codezeile so etwas wie sein TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;. Im DI-Denken hätte die Klasse einen Konstruktor wie public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }.
Tom Anderson

2
Und es wird zum Problem eines anderen, diesen Konstruktor aufzurufen. Ein DI-Framework würde dies mit einer globalen Karte tun. Eine handgefertigte DI-Architektur würde dies tun, da die mainMethode der App (oder einer ihrer Minions) die Abhängigkeit erstellen und dann den Konstruktor aufrufen würde. Im Wesentlichen ist die Verwendung einer globalen Variablen (oder einer globalen Methode) nur eine einfache Form des gefürchteten Dienstlokalisierungsmusters und kann wie jede andere Verwendung dieses Musters durch Abhängigkeitsinjektion ersetzt werden.
Tom Anderson

@ TomAnderson Ich bin wirklich verwirrt darüber, warum die Leute das Service Locator-Muster "fürchten". Ich denke, in den meisten Fällen ist es übertrieben oder wird bestenfalls nicht benötigt, aber es gibt scheinbar nützliche Fälle. Bei einer geringeren Anzahl von Parametern wird DI definitiv bevorzugt, aber stellen Sie sich 20+ vor. Zu sagen, dass der Code nicht strukturiert ist, ist kein gültiges Argument, da Gruppierungen von Parametern manchmal einfach keinen Sinn ergeben. Aus Sicht der Unit-Tests ist es mir auch egal, ob der Service getestet wird, sondern nur die Geschäftslogik. Wenn er richtig codiert ist, ist dies einfach. Ich habe diesen Bedarf nur in sehr großen Projekten gesehen.
Brandon Ling

20

Überlegen Sie wirklich, warum Sie einen Singleton benötigen, bevor Sie ihn schreiben. Es gibt eine quasi-religiöse Debatte über deren Verwendung, über die Sie leicht stolpern können, wenn Sie Singletons in Java googeln.

Persönlich versuche ich aus vielen Gründen, Singletons so oft wie möglich zu vermeiden. Die meisten davon finden sich auch beim Googeln von Singletons. Ich habe das Gefühl, dass Singletons ziemlich oft missbraucht werden, weil sie für jeden leicht verständlich sind, als Mechanismus verwendet werden, um "globale" Daten in ein OO-Design zu integrieren, und weil sie das Management des Objektlebenszyklus (oder) einfach umgehen können wirklich darüber nachdenken, wie man A von innen heraus machen kann B). Schauen Sie sich Dinge wie Inversion of Control (IoC) oder Dependency Injection (DI) an, um einen schönen Mittelweg zu finden.

Wenn Sie wirklich einen brauchen, hat Wikipedia ein gutes Beispiel für eine ordnungsgemäße Implementierung eines Singletons.


Einverstanden. Es handelt sich eher um eine grundlegende Klasse, die den Rest Ihrer Anwendung in Schwung bringt. Wenn sie dupliziert wird, kommt es zu einem völligen Chaos (dh einmaliger Zugriff auf eine Ressource oder Durchsetzung der Sicherheit). Die Weitergabe globaler Daten in Ihrer gesamten Anwendung ist eine große rote Fahne. Verwenden Sie es, wenn Sie bestätigen, dass Sie es wirklich brauchen.
Salvador Valencia

16

Es folgen 3 verschiedene Ansätze

1) Aufzählung

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

2) Double Checked Locking / Lazy Loading

/**
* Singleton pattern example with Double checked Locking
*/
public class DoubleCheckedLockingSingleton{
     private static volatile DoubleCheckedLockingSingleton INSTANCE;

     private DoubleCheckedLockingSingleton(){}

     public static DoubleCheckedLockingSingleton getInstance(){
         if(INSTANCE == null){
            synchronized(DoubleCheckedLockingSingleton.class){
                //double checking Singleton instance
                if(INSTANCE == null){
                    INSTANCE = new DoubleCheckedLockingSingleton();
                }
            }
         }
         return INSTANCE;
     }
}

3) Statische Fabrikmethode

/**
* Singleton pattern example with static factory method
*/

public class Singleton{
    //initailzed during class loading
    private static final Singleton INSTANCE = new Singleton();

    //to prevent creating another instance of Singleton
    private Singleton(){}

    public static Singleton getSingleton(){
        return INSTANCE;
    }
}

13

Ich verwende das Spring Framework, um meine Singletons zu verwalten. Es erzwingt nicht die "Singleton-Ness" der Klasse (was Sie sowieso nicht wirklich tun können, wenn mehrere Klassenlader beteiligt sind), bietet jedoch eine wirklich einfache Möglichkeit, verschiedene Fabriken zum Erstellen verschiedener Objekttypen zu erstellen und zu konfigurieren.


11

Wikipedia hat einige Beispiele für Singletons, auch in Java. Die Java 5-Implementierung sieht ziemlich vollständig aus und ist threadsicher (doppelt überprüfte Sperre angewendet).


11

Version 1:

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

Faules Laden, Thread-sicher mit Blockierung, geringe Leistung wegen synchronized.

Version 2:

public class MySingleton {
    private MySingleton() {}
    private static class MySingletonHolder {
        public final static MySingleton instance = new MySingleton();
    }
    public static MySingleton getInstance() {
        return MySingletonHolder.instance;
    }
}

Lazy Loading, Thread-sicher mit nicht blockierend, hohe Leistung.


10

Wenn Sie kein verzögertes Laden benötigen, versuchen Sie es einfach

public class Singleton {
    private final static Singleton INSTANCE = new Singleton();

    private Singleton() {}

    public static Singleton getInstance() { return Singleton.INSTANCE; }

    protected Object clone() {
        throw new CloneNotSupportedException();
    }
}

Wenn Sie ein verzögertes Laden wünschen und Ihr Singleton threadsicher sein soll, versuchen Sie es mit dem Muster der doppelten Überprüfung

public class Singleton {
        private static Singleton instance = null;

        private Singleton() {}

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

        protected Object clone() {
            throw new CloneNotSupportedException();
        }
}

Da die doppelte Überprüfung nicht garantiert funktioniert (aufgrund eines Problems mit Compilern weiß ich nichts mehr darüber), können Sie auch versuchen, die gesamte getInstance-Methode zu synchronisieren oder eine Registrierung für alle Ihre Singletons zu erstellen.


2
Die erste Version ist am besten. Angenommen, die Klasse führt nichts anderes als einen Singleton aus, wird sie normalerweise aufgrund des verzögerten Ladens der Klasse ungefähr an der gleichen Stelle wie in der zweiten Version instanziiert.
Dan Dyer

1
Eine doppelte Überprüfung ist für eine statische Aufladung sinnlos. Und warum haben Sie die geschützte Klonmethode veröffentlicht?
Tom Hawtin - Tackline

1
-1 Ihre Version der doppelt überprüften Sperre ist defekt.
Assylias

5
Außerdem müssen Sie Ihre Singleton-Variable volatile
festlegen

Die erste Version ist faul und threadsicher.
Miha_x64

9

Ich würde Enum Singleton sagen

Singleton, das Enum in Java verwendet, ist im Allgemeinen eine Möglichkeit, Enum Singleton zu deklarieren. Enum Singleton kann eine Instanzvariable und eine Instanzmethode enthalten. Beachten Sie der Einfachheit halber auch, dass Sie bei Verwendung einer Instanzmethode die Thread-Sicherheit dieser Methode gewährleisten müssen, wenn sie den Status des Objekts überhaupt beeinflusst.

Die Verwendung einer Aufzählung ist sehr einfach zu implementieren und weist keine Nachteile in Bezug auf serialisierbare Objekte auf, die auf andere Weise umgangen werden müssen.

/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
        INSTANCE;
        public void execute (String arg) {
                //perform operation here
        }
}

Sie können Singleton.INSTANCEviel einfacher darauf zugreifen , als die getInstance()Methode in Singleton aufzurufen .

1.12 Serialisierung von Enum-Konstanten

Enum-Konstanten werden anders serialisiert als gewöhnliche serialisierbare oder externalisierbare Objekte. Die serialisierte Form einer Enum-Konstante besteht ausschließlich aus ihrem Namen; Feldwerte der Konstanten sind in der Form nicht vorhanden. Um eine Aufzählungskonstante zu serialisieren, wird ObjectOutputStreamder von der Namensmethode der Aufzählungskonstante zurückgegebene Wert geschrieben. ObjectInputStreamLiest den Konstantennamen aus dem Stream, um eine Enum-Konstante zu deserialisieren . Die deserialisierte Konstante wird dann durch Aufrufen der java.lang.Enum.valueOfMethode erhalten, wobei der Aufzählungstyp der Konstante zusammen mit dem Namen der empfangenen Konstante als Argumente übergeben wird. Wie andere serialisierbare oder externalisierbare Objekte können Enum-Konstanten als Ziele für Rückreferenzen fungieren, die anschließend im Serialisierungsstrom erscheinen.

Das Verfahren , mit der ENUM - Konstanten serialisiert werden , kann nicht angepasst werden: jeder klassenspezifischen writeObject, readObject, readObjectNoData, writeReplace, und readResolveMethoden definiert durch enum - Typen sind während der Serialisierung und Deserialisierung ignoriert. Dementsprechend sind serialPersistentFieldsoder serialVersionUIDwerden Felddeklarationen auch ignoriert - alle Enum - Typen haben eine feste serialVersionUIDvon 0L. Das Dokumentieren serialisierbarer Felder und Daten für Aufzählungstypen ist nicht erforderlich, da die Art der gesendeten Daten nicht variiert.

Zitiert aus Oracle-Dokumenten

Ein weiteres Problem bei herkömmlichen Singletons besteht darin, dass sie nach der Implementierung der SerializableSchnittstelle nicht mehr Singleton bleiben, da die readObject()Methode immer eine neue Instanz wie den Konstruktor in Java zurückgibt. Dies kann vermieden werden, indem readResolve()neu erstellte Instanzen verwendet und verworfen werden, indem sie wie unten beschrieben durch Singleton ersetzt werden

 // readResolve to prevent another instance of Singleton
 private Object readResolve(){
     return INSTANCE;
 }

Dies kann noch komplexer werden, wenn Ihre Singleton-Klasse den Status beibehält, da Sie sie vorübergehend machen müssen. In Enum Singleton wird die Serialisierung jedoch von JVM garantiert.


Gut gelesen

  1. Singleton-Muster
  2. Enums, Singletons und Deserialization
  3. Doppelte Überprüfung der Verriegelung und des Singleton-Musters

8
There are 4 ways to create a singleton in java.

1- eager initialization singleton

    public class Test{
        private static final Test test = new Test();
        private Test(){}
        public static Test getTest(){
            return test;
        }
    

2- lazy initialization singleton (thread safe)

    public class Test {
         private static volatile Test test;
         private Test(){}
         public static Test getTest() {
            if(test == null) {
                synchronized(Test.class) {
                    if(test == null){test = new Test();
                }
            }
         }

        return test;
    


3- Bill Pugh Singleton with Holder Pattern (Preferably the best one)

    public class Test {

        private Test(){}

        private static class TestHolder{
            private static final Test test = new Test();
        }

        public static Test getInstance(){
            return TestHolder.test;
        }
    }

4- enum singleton
      public enum MySingleton {
        INSTANCE;
    private MySingleton() {
        System.out.println("Here");
    }
}

(1) ist nicht eifrig, es ist faul aufgrund des Lademechanismus der JVM-Klasse.
Miha_x64

@ Miha_x64 Wann habe ich eifriges Laden gesagt, ich habe eifrige Initialisierung gesagt. Wenn du denkst, dass beide gleich sind, dann ist es eifriges Laden. Vielleicht solltest du ein Buch schreiben und die Fehler korrigieren, die von früheren Autoren wie Joshua Bloch begangen wurden.
Dheeraj Sachan

Effektives Java ist ein großartiges Buch, das jedoch definitiv bearbeitet werden muss.
Miha_x64

@ Miha_x64 was eifrig geladen wird, können Sie anhand eines Beispiels erklären
Dheeraj Sachan

Etwas "eifrig" zu tun bedeutet "so schnell wie möglich". Beispielsweise unterstützt Hibernate das Laden von Beziehungen eifrig, wenn dies explizit erforderlich ist.
Miha_x64

8

Es könnte etwas spät sein, dies zu tun, aber die Implementierung eines Singletons ist sehr nuanciert. Das Haltermuster kann in vielen Situationen nicht verwendet werden. Und IMO, wenn Sie eine flüchtige Variable verwenden - Sie sollten auch eine lokale Variable verwenden. Beginnen wir am Anfang und wiederholen wir das Problem. Du wirst sehen, was ich meine.


Der erste Versuch könnte ungefähr so ​​aussehen:

public class MySingleton {

     private static MySingleton INSTANCE;

     public static MySingleton getInstance() {
        if (INSTANCE == null) {
            INSTANCE = new MySingleton();
        }

        return INSTANCE;
    }
    ...
}

Hier haben wir die MySingleton-Klasse, die ein privates statisches Mitglied namens INSTANCE und eine öffentliche statische Methode namens getInstance () hat. Beim ersten Aufruf von getInstance () ist das INSTANCE-Mitglied null. Der Flow fällt dann in die Erstellungsbedingung und erstellt eine neue Instanz der MySingleton-Klasse. Nachfolgende Aufrufe von getInstance () stellen fest, dass die Variable INSTANCE bereits festgelegt ist, und erstellen daher keine weitere MySingleton-Instanz. Dadurch wird sichergestellt, dass es nur eine Instanz von MySingleton gibt, die von allen Aufrufern von getInstance () gemeinsam genutzt wird.

Diese Implementierung hat jedoch ein Problem. Multithread-Anwendungen haben eine Race-Bedingung für die Erstellung der einzelnen Instanz. Wenn mehrere Ausführungsthreads gleichzeitig die Methode getInstance () treffen (oder ungefähr), wird das INSTANCE-Mitglied jeweils als null angezeigt. Dies führt dazu, dass jeder Thread eine neue MySingleton-Instanz erstellt und anschließend das INSTANCE-Mitglied festlegt.


private static MySingleton INSTANCE;

public static synchronized MySingleton getInstance() {
    if (INSTANCE == null) {
        INSTANCE = new MySingleton();
    }

    return INSTANCE;
}

Hier haben wir das Schlüsselwort synchronized in der Methodensignatur verwendet, um die Methode getInstance () zu synchronisieren. Dies wird sicherlich unseren Rennzustand verbessern. Threads blockieren nun die Methode und geben sie einzeln ein. Es entsteht aber auch ein Leistungsproblem. Diese Implementierung synchronisiert nicht nur die Erstellung der einzelnen Instanz, sondern synchronisiert alle Aufrufe von getInstance (), einschließlich Lesevorgängen. Lesevorgänge müssen nicht synchronisiert werden, da sie einfach den Wert von INSTANCE zurückgeben. Da Lesevorgänge den Großteil unserer Aufrufe ausmachen (denken Sie daran, dass die Instanziierung nur beim ersten Aufruf erfolgt), wird durch die Synchronisierung der gesamten Methode ein unnötiger Leistungseinbruch verursacht.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronize(MySingleton.class) {
            INSTANCE = new MySingleton();
        }
    }

    return INSTANCE;
}

Hier haben wir die Synchronisation von der Methodensignatur in einen synchronisierten Block verschoben, der die Erstellung der MySingleton-Instanz umschließt. Aber löst dies unser Problem? Nun, wir blockieren keine Lesevorgänge mehr, aber wir haben auch einen Schritt zurück gemacht. Mehrere Threads treffen gleichzeitig oder ungefähr zur gleichen Zeit auf die Methode getInstance () und alle sehen das INSTANCE-Mitglied als null. Sie treffen dann den synchronisierten Block, wo man die Sperre erhält und die Instanz erstellt. Wenn dieser Thread den Block verlässt, kämpfen die anderen Threads um die Sperre, und jeder Thread fällt nacheinander durch den Block und erstellt eine neue Instanz unserer Klasse. Wir sind also wieder da, wo wir angefangen haben.


private static MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Hier stellen wir einen weiteren Check von INSIDE the block aus. Wenn das INSTANCE-Mitglied bereits festgelegt wurde, überspringen wir die Initialisierung. Dies wird als doppelt geprüftes Sperren bezeichnet.

Dies löst unser Problem der Mehrfachinstanziierung. Aber auch hier hat unsere Lösung eine weitere Herausforderung dargestellt. Andere Threads sehen möglicherweise nicht, dass das INSTANCE-Mitglied aktualisiert wurde. Dies liegt daran, wie Java die Speicheroperationen optimiert. Threads kopieren die ursprünglichen Werte von Variablen aus dem Hauptspeicher in den Cache der CPU. Änderungen an Werten werden dann in diesen Cache geschrieben und aus diesem gelesen. Dies ist eine Funktion von Java zur Optimierung der Leistung. Dies schafft jedoch ein Problem für unsere Singleton-Implementierung. Ein zweiter Thread, der von einer anderen CPU oder einem anderen Kern unter Verwendung eines anderen Caches verarbeitet wird, sieht die vom ersten vorgenommenen Änderungen nicht. Dies führt dazu, dass der zweite Thread das INSTANCE-Mitglied als null ansieht und das Erstellen einer neuen Instanz unseres Singletons erzwingt.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    if (INSTANCE == null) {
        synchronized(MySingleton.class) {
            if (INSTANCE == null) {
                INSTANCE = createInstance();
            }
        }
    }

    return INSTANCE;
}

Wir lösen dies, indem wir das flüchtige Schlüsselwort in der Deklaration des INSTANCE-Mitglieds verwenden. Dadurch wird der Compiler angewiesen, immer aus dem Hauptspeicher und nicht aus dem CPU-Cache zu lesen und in diesen zu schreiben.

Diese einfache Änderung ist jedoch mit Kosten verbunden. Da wir den CPU-Cache umgehen, werden wir jedes Mal, wenn wir mit dem flüchtigen INSTANCE-Mitglied arbeiten, einen Leistungseinbruch erleiden - was wir viermal tun. Wir überprüfen die Existenz (1 und 2), setzen den Wert (3) und geben dann den Wert (4) zurück. Man könnte argumentieren, dass dieser Pfad der Randfall ist, da wir die Instanz nur beim ersten Aufruf der Methode erstellen. Vielleicht ist ein Leistungseinbruch bei der Erstellung erträglich. Aber selbst unser Hauptanwendungsfall, liest, wird das flüchtige Mitglied zweimal bearbeiten. Einmal, um die Existenz zu überprüfen und erneut, um ihren Wert zurückzugeben.


private static volatile MySingleton INSTANCE;

public static MySingleton getInstance() {
    MySingleton result = INSTANCE;
    if (result == null) {
        synchronized(MySingleton.class) {
            result = INSTANCE;
            if (result == null) {
                INSTANCE = result = createInstance();
            }
        }
    }

    return result;
}

Da der Leistungseinbruch darauf zurückzuführen ist, dass das flüchtige Element direkt bearbeitet wird, setzen wir eine lokale Variable auf den Wert des flüchtigen Elements und arbeiten stattdessen mit der lokalen Variablen. Dies wird die Häufigkeit verringern, mit der wir auf dem volatilen Markt operieren, wodurch ein Teil unserer Leistungsverluste zurückgewonnen wird. Beachten Sie, dass wir unsere lokale Variable erneut setzen müssen, wenn wir den synchronisierten Block eingeben. Dies stellt sicher, dass alle Änderungen, die während des Wartens auf das Schloss vorgenommen wurden, auf dem neuesten Stand sind.

Ich habe kürzlich einen Artikel darüber geschrieben. Den Singleton dekonstruieren . Dort finden Sie weitere Informationen zu diesen Beispielen und ein Beispiel für das "Halter" -Muster. Es gibt auch ein reales Beispiel, das den doppelt überprüften volatilen Ansatz zeigt. Hoffe das hilft.


Könnten Sie bitte erklären, warum dies BearerToken instancein Ihrem Artikel nicht der Fall ist static? Und was ist das result.hasExpired()?
Woland

Und was ist class MySingleton- vielleicht sollte es sein final?
Woland

1
@Woland Die BearerTokenInstanz ist nicht statisch, da sie Teil der BearerTokenFactory - ist, die mit einem bestimmten Autorisierungsserver konfiguriert ist. Es kann viele BearerTokenFactoryObjekte geben - jedes hat sein eigenes "Cache" BearerToken, das es verteilt, bis es abgelaufen ist. Die hasExpired()Methode auf dem BeraerTokenwird in der Factory- get()Methode aufgerufen , um sicherzustellen, dass kein abgelaufenes Token ausgegeben wird. Wenn abgelaufen, wird ein neues Token vom Autorisierungsserver angefordert. Der Absatz nach dem Codeblock erläutert dies ausführlicher.
Michael Andrews

4

So implementieren Sie ein einfaches singleton:

public class Singleton {
    // It must be static and final to prevent later modification
    private static final Singleton INSTANCE = new Singleton();
    /** The constructor must be private to prevent external instantiation */ 
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return INSTANCE;
    }
}

So erstellen Sie richtig faul singleton:

public class Singleton {
    // The constructor must be private to prevent external instantiation   
    private Singleton(){}
    /** The public static method allowing to get the instance */
    public static Singleton getInstance() {
        return SingletonHolder.INSTANCE;
    }
    /** 
     * The static inner class responsible for creating your instance only on demand,
     * because the static fields of a class are only initialized when the class
     * is explicitly called and a class initialization is synchronized such that only 
     * one thread can perform it, this rule is also applicable to inner static class
     * So here INSTANCE will be created only when SingletonHolder.INSTANCE 
     * will be called
     */
    private static class SingletonHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

Beide sind faul, vorausgesetzt, das einzige, was Sie von Singleton benötigen, ist seine Instanz.
Miha_x64

@ Miha_x64 Der erste Fall instanziiert den Singleton, wenn die JVM die Klasse initialisiert, der zweite Fall instanziiert den Singleton nur beim Aufruf getInstance(). Aber in der Tat, wenn Sie keine anderen statischen Methoden in Ihrer Klasse haben Singletonund nur aufrufen, getInstance()gibt es keinen wirklichen Unterschied.
Nicolas Filotto

3

Sie müssen die Redewendung überprüfen, wenn Sie die Instanzvariable einer Klasse träge laden müssen. Wenn Sie eine statische Variable oder einen Singleton träge laden müssen, müssen Sie die Initiierung bei Bedarf in Anspruch nehmen .

Wenn der Singleton seriliazble sein muss, müssen außerdem alle anderen Felder vorübergehend sein und die readResolve () -Methode muss implementiert werden, um das Singleton-Objekt invariant zu halten. Andernfalls wird jedes Mal, wenn das Objekt deserialisiert wird, eine neue Instanz des Objekts erstellt. ReadResolve () ersetzt das neue Objekt, das von readObject () gelesen wurde, wodurch das neue Objekt zur Speicherbereinigung gezwungen wurde, da keine Variable darauf verweist.

public static final INSTANCE == ....
private Object readResolve() {
  return INSTANCE; // original singleton instance.
} 

3

Verschiedene Möglichkeiten, ein Singleton-Objekt zu erstellen:

  1. Laut Joshua Bloch wäre Enum das Beste.

  2. Sie können auch Double Check Locking verwenden.

  3. Sogar die innere statische Klasse kann verwendet werden.


3

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");
  }

}

2

Verwenden Sie für JSE 5.0 und höher den Enum-Ansatz, andernfalls verwenden Sie den statischen Singleton-Holder-Ansatz ((ein von Bill Pugh beschriebener Lazy-Loading-Ansatz). Die letzte Lösung ist auch threadsicher, ohne dass spezielle Sprachkonstrukte erforderlich sind (dh flüchtig oder synchronisiert).


2

Ein weiteres Argument, das häufig gegen Singletons verwendet wird, sind ihre Testbarkeitsprobleme. Singletons sind zu Testzwecken nicht leicht zu verspotten. Wenn sich herausstellt, dass dies ein Problem ist, möchte ich die folgenden geringfügigen Änderungen vornehmen:

public class SingletonImpl {

    private static SingletonImpl instance;

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

    public static void setInstance(SingletonImpl impl) {
        instance = impl;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

Die hinzugefügte setInstanceMethode ermöglicht das Festlegen einer Modellimplementierung der Singleton-Klasse während des Testens:

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Dies funktioniert auch mit frühen Initialisierungsansätzen:

public class SingletonImpl {

    private static final SingletonImpl instance = new SingletonImpl();

    private static SingletonImpl alt;

    public static void setInstance(SingletonImpl inst) {
        alt = inst;
    }

    public static SingletonImpl getInstance() {
        if (alt != null) {
            return alt;
        }
        return instance;
    }

    public void a() {
        System.out.println("Default Method");
    }
}

public class SingletonMock extends SingletonImpl {

    @Override
    public void a() {
        System.out.println("Mock Method");
    }

}

Dies hat den Nachteil, dass diese Funktionalität auch der normalen Anwendung zugänglich gemacht wird. Andere Entwickler, die an diesem Code arbeiten, könnten versucht sein, die Methode "setInstance" zu verwenden, um eine bestimmte Funktion und damit das gesamte Anwendungsverhalten zu ändern. Daher sollte diese Methode mindestens eine gute Warnung in ihrem Javadoc enthalten.

Für die Möglichkeit von Mockup-Tests (falls erforderlich) kann diese Code-Exposition jedoch ein akzeptabler Preis sein.


1

einfachste Singleton-Klasse

public class Singleton {
  private static Singleton singleInstance = new Singleton();
  private Singleton() {}
  public static Singleton getSingleInstance() {
    return singleInstance;
  }
}

1
Dies ist das gleiche wie Jonathans Antwort unten
oder

1
Duplikat dieser Geschwisterantwort von Jonathan, die fünf Jahre zuvor veröffentlicht wurde. Siehe diese Antwort für interessante Kommentare.
Basil Bourque

0

Ich denke immer noch, dass enum nach Java 1.5 die beste verfügbare Singleton-Implementierung ist, da es auch sicherstellt, dass selbst in Umgebungen mit mehreren Threads nur eine Instanz erstellt wird.

public enum Singleton{ INSTANCE; }

und du bist fertig !!!


1
Dies wird bereits vor Jahren in anderen Antworten erwähnt.
Pang

0

Schauen Sie sich diesen Beitrag an.

Beispiele für GoF-Entwurfsmuster in Javas Kernbibliotheken

Aus dem Abschnitt "Singleton" der besten Antwort:

Singleton (erkennbar an Erstellungsmethoden, die jedes Mal dieselbe Instanz (normalerweise von sich selbst) zurückgeben)

  • java.lang.Runtime # getRuntime ()
  • java.awt.Desktop # getDesktop ()
  • java.lang.System # getSecurityManager ()

Sie können das Beispiel von Singleton auch aus nativen Java-Klassen selbst lernen.


0

Das beste Singleton-Muster, das ich je gesehen habe, verwendet die Lieferantenschnittstelle.

  • Es ist generisch und wiederverwendbar
  • Es unterstützt die verzögerte Initialisierung
  • Es wird nur synchronisiert, bis es initialisiert wurde. Dann wird der blockierende Lieferant durch einen nicht blockierenden Lieferanten ersetzt.

Siehe unten:

public class Singleton<T> implements Supplier<T> {

    private boolean initialized;
    private Supplier<T> singletonSupplier;

    public Singleton(T singletonValue) {
        this.singletonSupplier = () -> singletonValue;
    }

    public Singleton(Supplier<T> supplier) {
        this.singletonSupplier = () -> {
            // The initial supplier is temporary; it will be replaced after initialization
            synchronized (supplier) {
                if (!initialized) {
                    T singletonValue = supplier.get();
                    // Now that the singleton value has been initialized,
                    // replace the blocking supplier with a non-blocking supplier
                    singletonSupplier = () -> singletonValue;
                    initialized = true;
                }
                return singletonSupplier.get();
            }
        };
    }

    @Override
    public T get() {
        return singletonSupplier.get();
    }
}

-3

Manchmal reicht ein einfaches " static Foo foo = new Foo();" nicht aus. Denken Sie nur an einige grundlegende Daten, die Sie einfügen möchten.

Andererseits müssten Sie jede Methode synchronisieren, die die Singleton-Variable als solche instanziiert. Die Synchronisierung ist als solche nicht schlecht, kann jedoch zu Leistungsproblemen oder Sperren führen (in sehr sehr seltenen Situationen anhand dieses Beispiels. Die Lösung ist

public class Singleton {

    private static Singleton instance = null;

    static {
          instance = new Singleton();
          // do some of your instantiation stuff here
    }

    private Singleton() {
          if(instance!=null) {
                  throw new ErrorYouWant("Singleton double-instantiation, should never happen!");
          }
    }

    public static getSingleton() {
          return instance;
    }

}

Was passiert nun? Die Klasse wird über den Klassenlader geladen. Direkt nachdem die Klasse aus einem Byte-Array interpretiert wurde, führt die VM den statischen {} - Block aus. Das ist das ganze Geheimnis: Der statische Block wird nur einmal aufgerufen, wenn die angegebene Klasse (Name) des angegebenen Pakets von diesem einen Klassenladeprogramm geladen wird.


3
Nicht wahr. statische Variablen werden zusammen mit statischen Blöcken beim Laden der Klasse initialisiert. Die Erklärung muss nicht aufgeteilt werden.
Craig P. Motlin

-5
public class Singleton {

    private static final Singleton INSTANCE = new Singleton();

    private Singleton(){
    if (INSTANCE != null)
        throw new IllegalStateException (“Already instantiated...”);
}

    public synchronized static Singleton getInstance() { 
    return INSTANCE;

    }

}

Da wir das Schlüsselwort Synchronized vor getInstance hinzugefügt haben, haben wir die Race-Bedingung für den Fall vermieden, dass zwei Threads gleichzeitig getInstance aufrufen.

Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.