Wie lässt sich ein Singleton-Entwurfsmuster in Java effizient implementieren?
Antworten
Zu viele Anzeigen?Verwenden Sie eine Aufzählung:
public enum Foo {
INSTANCE;
}
Joshua Bloch erläutert diesen Ansatz in seinem Effektives Java Reloaded Vortrag auf der Google I/O 2008: Link zum Video . Siehe auch die Folien 30-32 seiner Präsentation ( effektiv_java_reloaded.pdf ) :
Der richtige Weg zur Implementierung eines serialisierbaren Singletons
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
Editar: Eine Online-Teil von "Effektives Java" sagt:
"Dieser Ansatz ist funktional äquivalent zum Public-Field-Ansatz, mit dem Unterschied, dass er prägnanter ist, die Serialisierungsmaschinerie kostenlos zur Verfügung stellt und eine unumstößliche Garantie gegen Mehrfachinstanziierung bietet, selbst angesichts ausgefeilter Serialisierungs- oder Reflection-Angriffe. Auch wenn dieser Ansatz noch nicht weit verbreitet ist, ein Einzelelement-Enum-Typ ist der beste Weg, um ein Singleton zu implementieren ."
Je nach Verwendungszweck gibt es mehrere "richtige" Antworten.
Seit Java 5 ist es am besten, ein Enum zu verwenden:
public enum Foo {
INSTANCE;
}
Vor Java 5 ist der einfachste Fall der:
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");
}
}
Schauen wir uns den Code an. Zunächst soll die Klasse endgültig sein. In diesem Fall habe ich die final
um den Nutzern mitzuteilen, dass sie endgültig ist. Dann müssen Sie den Konstruktor privat machen, um zu verhindern, dass Benutzer ihre eigenen Foo erstellen. Das Auslösen einer Exception im Konstruktor verhindert, dass die Benutzer mit Hilfe von Reflection ein zweites Foo erstellen können. Dann erstellen Sie eine private static final Foo
Feld, das die einzige Instanz enthält, und ein public static Foo getInstance()
Methode, um sie zurückzugeben. Die Java-Spezifikation stellt sicher, dass der Konstruktor nur dann aufgerufen wird, wenn die Klasse zum ersten Mal verwendet wird.
Wenn Sie ein sehr großes Objekt oder einen schweren Konstruktionscode haben と auch andere zugängliche statische Methoden oder Felder haben, die verwendet werden könnten, bevor eine Instanz benötigt wird, dann und nur dann müssen Sie Lazy Initialization verwenden.
Sie können eine private static class
um die Instanz zu laden. Der Code würde dann wie folgt 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 Linie private static final Foo INSTANCE = new Foo();
wird nur dann ausgeführt, wenn die Klasse FooLoader tatsächlich verwendet wird. Dadurch wird die faule Instanziierung vermieden, und es ist garantiert, dass sie thread-sicher ist.
Wenn Sie Ihr Objekt auch serialisieren wollen, müssen Sie sicherstellen, dass bei der 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 readResolve()
stellt sicher, dass die einzige Instanz zurückgegeben wird, auch wenn das Objekt in einem früheren Programmlauf serialisiert wurde.
Haftungsausschluss: Ich habe einfach all die tollen Antworten zusammengefasst und in meinen eigenen Worten geschrieben.
Bei der Implementierung von Singleton haben wir zwei Möglichkeiten:
- Langsames Laden
- Frühes Laden
Lazy Loading fügt ein wenig Overhead hinzu (eine Menge, um ehrlich zu sein), also verwenden Sie es nur, wenn Sie ein sehr großes Objekt oder einen schweren Konstruktionscode haben と auch andere zugängliche statische Methoden oder Felder haben, die verwendet werden könnten, bevor eine Instanz benötigt wird, dann und nur dann müssen Sie die träge Initialisierung verwenden. Ansonsten ist das frühe Laden eine gute Wahl.
Die einfachste Art, ein 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, außer dass es ein früh geladenes Singleton ist. Versuchen wir es mit "lazy loaded 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 allein mit mehreren bösen Fäden kämpft, die viele viele Instanzen unseres Helden wollen. Also lasst uns ihn vor bösem Multi-Threading schützen:
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 unseren Helden zu schützen, wirklich!!! Das 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-Anweisung zu vergessen und schwer zu verstehen, warum sie notwendig ist. Für Details: Die Erklärung "Double-Checked Locking ist kaputt".
Jetzt sind wir uns über die bösen Fäden sicher, aber was ist mit der grausamen Serialisierung? Wir müssen sicherstellen, dass auch bei der De-Serialisierung kein neues Objekt erstellt wird:
class Foo implements Serializable {
private static final long serialVersionUID = 1L;
private static volatile Foo INSTANCE = null;
// The rest of the things are same as above
// No more fear of serialization
@SuppressWarnings("unused")
private Object readResolve() {
return INSTANCE;
}
}
Die Methode readResolve()
stellt sicher, dass die einzige Instanz zurückgegeben wird, auch wenn das Objekt in einem früheren Durchlauf unseres Programms serialisiert wurde.
Schließlich haben wir einen ausreichenden Schutz gegen Threads und Serialisierung hinzugefügt, aber unser Code sieht sperrig und hässlich aus. Verpassen 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 genau unser Held :)
Da die Linie private static final Foo INSTANCE = new Foo();
wird nur ausgeführt, wenn die Klasse FooLoader
tatsächlich verwendet wird, kümmert sich dies um die faule Instanziierung, und es ist garantiert, dass es thread-sicher ist.
Und wir sind schon so weit gekommen. Hier ist der beste Weg, um alles, was wir getan haben, auf die bestmögliche Weise zu erreichen:
public enum Foo {
INSTANCE;
}
Die intern behandelt werden wie
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
Das war's! Keine Angst mehr vor Serialisierung, Threads und hässlichem Code. Auch ENUMS-Singleton werden verzögert initialisiert .
Dieser Ansatz ist funktionell gleichwertig mit dem Ansatz des öffentlichen Bereichs, mit dem Unterschied, dass er prägnanter ist, die Serialisierungsmechanismen kostenlos zur Verfügung stellt und eine unumstößliche Garantie gegen mehrfache Instanziierung, selbst angesichts ausgefeilter Serialisierungs- oder Reflection-Angriffen. Auch wenn dieser Ansatz noch nicht weit verbreitet ist, ist ein Ein-Element-Enum-Typ der beste Weg, um ein Singleton zu implementieren.
-Joshua Bloch in "Effektives Java"
Jetzt haben Sie vielleicht begriffen, warum ENUMS als beste Methode zur Implementierung eines Singletons angesehen werden, und vielen Dank für Ihre Geduld :)
Aktualisierte es auf meinem Blog .
Die von Stu Thompson veröffentlichte Lösung ist in Java 5.0 und höher gültig. Ich würde es jedoch vorziehen, es nicht zu verwenden, weil ich es für fehleranfällig halte.
Die volatile-Anweisung ist leicht zu vergessen und schwer zu verstehen, warum sie notwendig ist. Ohne die volatile-Anweisung wäre dieser Code nicht mehr thread-sicher, da die Sperrung doppelt geprüft wird. Mehr dazu finden Sie in Abschnitt 16.2.4 von Java-Gleichzeitigkeit in der Praxis . Kurz gesagt: Dieses Muster (vor Java 5.0 oder ohne die volatile-Anweisung) könnte eine Referenz auf das Bar-Objekt zurückgeben, die sich (noch) in einem falschen Zustand befindet.
Dieses Muster wurde zur Leistungsoptimierung erfunden. Aber das ist nun wirklich kein wirkliches Anliegen mehr. Der folgende träge 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;
}
}
Threadsicher 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;
}
}
Achten Sie auf die volatile
Modifikator hier :) Er ist wichtig, weil ohne ihn das JMM (Java Memory Model) anderen Threads nicht garantiert, dass sie Änderungen an seinem Wert sehen. Die Synchronisation nicht kümmert sich darum - es serialisiert nur den Zugriff auf diesen Codeblock.
Die Antwort von @Bno beschreibt detailliert den von Bill Pugh (FindBugs) empfohlenen Ansatz und ist argumentativ besser. Lesen und bewerten Sie auch seine Antwort.
- See previous answers
- Weitere Antworten anzeigen