Was ist ein effizienter Weg, um ein Singleton-Muster in Java zu implementieren?
Was ist ein effizienter Weg, um ein Singleton-Muster in Java zu implementieren?
Antworten:
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 .“
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 final
Schlü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 Foo
Feld 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.
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 FooLoader
tatsä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 .
serialVersionUID
von 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.
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;
}
}
getBar()
. (Und wenn getBar
es "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
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 : volatile
Achten 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.
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;
}
}
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.
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):
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).
TicketNumberer
, die eine einzelne globale Instanz haben muss und in der Sie eine Klasse schreiben möchten, die eine TicketIssuer
Codezeile 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; }
.
main
Methode 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.
Ü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.
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;
}
}
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.
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.
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.
volatile
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.INSTANCE
viel 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
ObjectOutputStream
der von der Namensmethode der Aufzählungskonstante zurückgegebene Wert geschrieben.ObjectInputStream
Liest den Konstantennamen aus dem Stream, um eine Enum-Konstante zu deserialisieren . Die deserialisierte Konstante wird dann durch Aufrufen derjava.lang.Enum.valueOf
Methode 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
, undreadResolve
Methoden definiert durch enum - Typen sind während der Serialisierung und Deserialisierung ignoriert. Dementsprechend sindserialPersistentFields
oderserialVersionUID
werden Felddeklarationen auch ignoriert - alle Enum - Typen haben eine festeserialVersionUID
von0L
. Das Dokumentieren serialisierbarer Felder und Daten für Aufzählungstypen ist nicht erforderlich, da die Art der gesendeten Daten nicht variiert.
Ein weiteres Problem bei herkömmlichen Singletons besteht darin, dass sie nach der Implementierung der Serializable
Schnittstelle 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
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");
}
}
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.
class MySingleton
- vielleicht sollte es sein final
?
BearerToken
Instanz ist nicht statisch, da sie Teil der BearerTokenFactory
- ist, die mit einem bestimmten Autorisierungsserver konfiguriert ist. Es kann viele BearerTokenFactory
Objekte geben - jedes hat sein eigenes "Cache" BearerToken
, das es verteilt, bis es abgelaufen ist. Die hasExpired()
Methode auf dem BeraerToken
wird 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.
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();
}
}
getInstance()
. Aber in der Tat, wenn Sie keine anderen statischen Methoden in Ihrer Klasse haben Singleton
und nur aufrufen, getInstance()
gibt es keinen wirklichen Unterschied.
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.
}
Verschiedene Möglichkeiten, ein Singleton-Objekt zu erstellen:
Laut Joshua Bloch wäre Enum das Beste.
Sie können auch Double Check Locking verwenden.
Sogar die innere statische Klasse kann verwendet werden.
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");
}
}
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).
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 setInstance
Methode 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.
einfachste Singleton-Klasse
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
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 !!!
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.
Das beste Singleton-Muster, das ich je gesehen habe, verwendet die Lieferantenschnittstelle.
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();
}
}
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.
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.