Singleton ist eines der am weitesten verbreiteten Schöpfungsentwurfsmuster, um das von Anwendungen erstellte Objekt einzuschränken. Wenn Sie es in einer Multi-Thread-Umgebung verwenden, ist die Thread-Sicherheit der Singleton-Klasse sehr wichtig. In realen Anwendungen sind Ressourcen wie Datenbankverbindungen oder Enterprise-Informationssysteme (EIS) begrenzt und sollten klug genutzt werden, um Engpässe bei Ressourcen zu vermeiden. Um dies zu erreichen, können wir ein \textit{Singleton-Entwurfsmuster} implementieren. Wir können eine \textit{Wrapper-Klasse} für die Ressource erstellen und die Anzahl der zur Laufzeit erstellten Objekte auf eins begrenzen.
Thread-sicherer Singleton in Java
Im Allgemeinen folgen wir den folgenden Schritten, um eine Singleton-Klasse zu erstellen:
- Erstellen Sie den privaten \textit{Konstruktor}, um die Erstellung eines neuen Objekts mit dem new-Operator zu vermeiden.
- Deklarieren Sie eine private \textit{statische} Instanz derselben Klasse.
- Bieten Sie eine öffentliche statische Methode an, die die Singleton-Klasseninstanzvariable zurückgibt. Wenn die Variable nicht initialisiert ist, initialisieren Sie sie, ansonsten geben Sie einfach die Instanzvariable zurück.
Mit den oben genannten Schritten habe ich eine Singleton-Klasse erstellt, die folgendermaßen aussieht. ASingleton.java
package com.journaldev.designpatterns;
public class ASingleton {
private static ASingleton instance = null;
private ASingleton() {
}
public static ASingleton getInstance() {
if (instance == null) {
instance = new ASingleton();
}
return instance;
}
}
In dem obigen Code ist die Methode getInstance() nicht threadsicher. Mehrere Threads können gleichzeitig darauf zugreifen. Für die ersten Threads, wenn die Instanzvariable nicht initialisiert ist, können mehrere Threads in die if-Schleife eintreten und mehrere Instanzen erstellen. Dadurch wird unsere Singleton-Implementierung unterbrochen.
Wie erreicht man Thread-Sicherheit in einer Singleton-Klasse?
Es gibt drei Möglichkeiten, wie wir Thread-Sicherheit erreichen können.
- Erstellen Sie die Instanzvariable zur Zeit des Klassenladens.
Vorteile:
- Thread-Sicherheit ohne Synchronisierung
- Einfach zu implementieren
Nachteile:
- Frühe Erstellung einer Ressource, die in der Anwendung möglicherweise nicht verwendet wird.
- Die Client-Anwendung kann kein Argument übergeben, daher können wir es nicht wiederverwenden. Zum Beispiel eine generische Singleton-Klasse für Datenbankverbindungen, bei der die Client-Anwendung Datenbankservereigenschaften bereitstellt.
- Synchronisieren Sie die getInstance()-Methode.
Vorteile:
- Die Thread-Sicherheit ist garantiert.
- Die Client-Anwendung kann Parameter übergeben
- Die verzögerte Initialisierung wurde erreicht
Nachteile:
- Langsame Leistung aufgrund von Sperrüberkopf.
- Unnötige Synchronisierung, die nicht mehr erforderlich ist, sobald die Instanzvariable initialisiert ist.
- Verwenden Sie einen synchronisierten Block innerhalb der if-Schleife und eine volatile Variable
Vorteile:
- Die Thread-Sicherheit ist garantiert
- Die Client-Anwendung kann Argumente übergeben
- Die verzögerte Initialisierung wurde erreicht
- Die Synchronisierungsüberlastung ist minimal und gilt nur für die ersten paar Threads, wenn die Variable null ist.
Nachteile:
- Zusätzliche if-Bedingung
Wenn wir alle drei Möglichkeiten zur Erreichung der Thread-Sicherheit betrachten, denke ich, dass die dritte die beste Option ist. In diesem Fall wird die modifizierte Klasse folgendermaßen aussehen:
package com.journaldev.designpatterns;
public class ASingleton {
private static volatile ASingleton instance;
private static Object mutex = new Object();
private ASingleton() {
}
public static ASingleton getInstance() {
ASingleton result = instance;
if (result == null) {
synchronized (mutex) {
result = instance;
if (result == null)
instance = result = new ASingleton();
}
}
return result;
}
}
Die lokale Variable result
scheint unnötig zu sein. Aber sie ist da, um die Leistung unseres Codes zu verbessern. In Fällen, in denen die Instanz bereits initialisiert ist (meistens), wird das volatile Feld nur einmal abgerufen (aufgrund von „return result;“ anstelle von „return instance;“). Dies kann die Gesamtleistung der Methode um bis zu 25 Prozent verbessern. Wenn Sie denken, dass es bessere Möglichkeiten gibt, dies zu erreichen, oder wenn die Thread-Sicherheit bei der obigen Implementierung gefährdet ist, kommentieren Sie bitte und teilen Sie es mit uns allen.
Bonus-Tipp
String ist kein sehr guter Kandidat, um mit dem Schlüsselwort synchronized verwendet zu werden. Das liegt daran, dass sie im String-Pool gespeichert sind und wir nicht möchten, dass ein String gesperrt wird, der möglicherweise von einem anderen Code verwendet wird. Deshalb verwende ich eine Objektvariable. Erfahren Sie mehr über Synchronisation und Thread-Sicherheit in Java.
Sie können weitere Java-Beispiele aus unserem GitHub-Repository überprüfen.
Source:
https://www.digitalocean.com/community/tutorials/thread-safety-in-java-singleton-classes