Quel est un moyen efficace d'implémenter un modèle singleton en Java?
Quel est un moyen efficace d'implémenter un modèle singleton en Java?
Réponses:
Utilisez une énumération:
public enum Foo {
INSTANCE;
}
Joshua Bloch a expliqué cette approche dans son exposé Effective Java Reloaded à Google I / O 2008: lien vers la vidéo . Voir également les diapositives 30-32 de sa présentation ( effective_java_reloaded.pdf ):
La bonne façon de mettre en œuvre un singleton sérialisable
public enum Elvis { INSTANCE; private final String[] favoriteSongs = { "Hound Dog", "Heartbreak Hotel" }; public void printFavorites() { System.out.println(Arrays.toString(favoriteSongs)); } }
Edit: Une partie en ligne de "Java efficace" dit:
"Cette approche est fonctionnellement équivalente à l'approche du domaine public, sauf qu'elle est plus concise, fournit gratuitement le mécanisme de sérialisation et offre une garantie à toute épreuve contre les instanciations multiples, même face à des attaques sophistiquées de sérialisation ou de réflexion. Bien que cette approche ait encore à être largement adopté, un type d'énumération à un seul élément est le meilleur moyen d'implémenter un singleton . "
Selon l'utilisation, il existe plusieurs réponses «correctes».
Depuis java5, la meilleure façon de le faire est d'utiliser une énumération:
public enum Foo {
INSTANCE;
}
Avant java5, le cas le plus simple est:
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");
}
}
Passons en revue le code. Tout d'abord, vous voulez que la classe soit finale. Dans ce cas, j'ai utilisé le final
mot clé pour faire savoir aux utilisateurs qu'il est définitif. Ensuite, vous devez rendre le constructeur privé pour empêcher les utilisateurs de créer leur propre Foo. Le fait de lever une exception du constructeur empêche les utilisateurs d'utiliser la réflexion pour créer un deuxième Foo. Ensuite, vous créez un private static final Foo
champ pour contenir la seule instance et une public static Foo getInstance()
méthode pour la renvoyer. La spécification Java garantit que le constructeur n'est appelé que lors de la première utilisation de la classe.
Lorsque vous avez un très grand objet ou un code de construction lourd ET que vous avez également d'autres méthodes ou champs statiques accessibles qui pourraient être utilisés avant qu'une instance ne soit nécessaire, alors et seulement alors vous devez utiliser l'initialisation paresseuse.
Vous pouvez utiliser un private static class
pour charger l'instance. Le code ressemblerait alors à:
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;
}
}
Étant donné que la ligne private static final Foo INSTANCE = new Foo();
n'est exécutée que lorsque la classe FooLoader est réellement utilisée, cela prend soin de l'instanciation paresseuse et est garantie d'être thread-safe.
Lorsque vous souhaitez également pouvoir sérialiser votre objet, vous devez vous assurer que la désérialisation ne créera pas de copie.
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;
}
}
La méthode readResolve()
s'assurera que la seule instance sera renvoyée, même lorsque l'objet a été sérialisé lors d'une précédente exécution de votre programme.
Avertissement: je viens de résumer toutes les réponses impressionnantes et de l'écrire dans mes mots.
Lors de la mise en œuvre de Singleton, nous avons 2 options
1. Chargement paresseux
2. Chargement précoce
Le chargement différé ajoute un peu de surcharge (pour être honnête), utilisez-le uniquement lorsque vous avez un objet très volumineux ou un code de construction lourd ET que vous avez également d'autres méthodes ou champs statiques accessibles qui pourraient être utilisés avant qu'une instance ne soit nécessaire, alors et seulement alors vous devez utiliser l'initialisation paresseuse. Sinon, choisir un chargement précoce est un bon choix.
Le moyen le plus simple de mettre en œuvre Singleton est
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;
}
}
Tout va bien sauf son singleton chargé tôt. Permet d'essayer un singleton paresseux
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;
}
}
Jusqu'ici tout va bien mais notre héros ne survivra pas en se battant seul avec plusieurs fils diaboliques qui veulent de nombreuses instances de notre héros. Permet donc de le protéger du diabolique multi-threading
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;
}
}
mais il ne suffit pas de protéger le héros, vraiment !!! C'est le mieux que nous pouvons / devrions faire pour aider notre héros
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;
}
}
Ceci est appelé "idiome de verrouillage à double vérification". Il est facile d'oublier la déclaration volatile et difficile de comprendre pourquoi elle est nécessaire.
Pour plus de détails: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html
Maintenant, nous sommes sûrs du mauvais fil, mais qu'en est-il de la sérieuse sérialisation? Nous devons nous assurer que même pendant la dé-sérialisation aucun nouvel objet n'est créé
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;
}
}
La méthode readResolve()
s'assurera que la seule instance sera retournée, même lorsque l'objet a été sérialisé lors d'une précédente exécution de notre programme.
Enfin, nous avons ajouté suffisamment de protection contre les threads et la sérialisation, mais notre code semble volumineux et laid. Laissons notre héros se refaire une beauté
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;
}
}
Oui, c'est notre même héros :)
Puisque la ligne private static final Foo INSTANCE = new Foo();
n'est exécutée que lorsque la classeFooLoader
est réellement utilisée, cela prend soin de l'instanciation paresseuse,
et est-il garanti être thread-safe.
Et nous sommes venus si loin, voici la meilleure façon de réaliser tout ce que nous avons fait est la meilleure façon possible
public enum Foo {
INSTANCE;
}
Qui en interne sera traité comme
public class Foo {
// It will be our sole hero
private static final Foo INSTANCE = new Foo();
}
C'est ça! Plus de peur de la sérialisation, des threads et du code laid. Les singleton ENUMS sont également initialisés paresseusement .
Cette approche est fonctionnellement équivalente à l'approche du domaine public, sauf qu'elle est plus concise, fournit gratuitement le mécanisme de sérialisation et offre une garantie à toute épreuve contre les instanciations multiples, même face à des attaques sophistiquées de sérialisation ou de réflexion. Bien que cette approche n'ait pas encore été largement adoptée, un type d'énumération à élément unique est le meilleur moyen d'implémenter un singleton.
-Joshua Bloch dans "Java efficace"
Maintenant, vous avez peut-être compris pourquoi ENUMS est considéré comme le meilleur moyen d'implémenter Singleton et merci pour votre patience :)
Mis à jour sur mon blog .
serialVersionUID
de 0L
. Troisième problème: pas de personnalisation: les méthodes writeObject, readObject, readObjectNoData, writeReplace et readResolve spécifiques à la classe définies par les types enum sont ignorées pendant la sérialisation et la désérialisation.
La solution publiée par Stu Thompson est valide dans Java5.0 et versions ultérieures. Mais je préférerais ne pas l'utiliser car je pense qu'il est sujet aux erreurs.
Il est facile d'oublier la déclaration volatile et difficile de comprendre pourquoi elle est nécessaire. Sans le volatile, ce code ne serait plus sûr pour les threads en raison de l'anti-modèle de verrouillage à double vérification. Voir plus à ce sujet au paragraphe 16.2.4 de la concurrence Java en pratique . En bref: ce modèle (avant Java5.0 ou sans l'instruction volatile) pourrait renvoyer une référence à l'objet Bar qui est (toujours) dans un état incorrect.
Ce modèle a été inventé pour l'optimisation des performances. Mais ce n'est vraiment plus une réelle préoccupation. Le code d'initialisation paresseux suivant est rapide et, plus important encore, plus facile à lire.
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
getBar()
. (Et s'il getBar
est appelé "trop tôt", vous rencontrerez le même problème, peu importe la façon dont les singleons sont implémentés.) Vous pouvez voir le chargement de classe paresseux du code ci-dessus ici: pastebin.com/iq2eayiR
Thread safe en 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 : faites attention au volatile
modificateur ici. :) C'est important car sans lui, les autres threads ne sont pas garantis par le JMM (Java Memory Model) pour voir les changements de sa valeur. La synchronisation ne s'occupe pas de cela - elle ne sérialise que l'accès à ce bloc de code.
EDIT 2 : La réponse de @Bno détaille l'approche recommandée par Bill Pugh (FindBugs) et est sans doute meilleure. Allez lire et voter sa réponse aussi.
Oubliez l' initialisation paresseuse , c'est trop problématique. C'est la solution la plus simple:
public class A {
private static final A INSTANCE = new A();
private A() {}
public static A getInstance() {
return INSTANCE;
}
}
Assurez-vous que vous en avez vraiment besoin. Faites un google pour "singleton anti-pattern" pour voir quelques arguments contre. Il n'y a rien de fondamentalement mauvais, je suppose, mais c'est juste un mécanisme pour exposer des ressources / données globales, alors assurez-vous que c'est la meilleure façon. En particulier, j'ai trouvé l'injection de dépendances plus utile, en particulier si vous utilisez également des tests unitaires, car DI vous permet d'utiliser des ressources simulées à des fins de test.
Je suis mystifié par certaines des réponses qui suggèrent DI comme une alternative à l'utilisation de singletons; ce sont des concepts sans rapport. Vous pouvez utiliser DI pour injecter des instances singleton ou non singleton (par exemple par thread). Au moins, cela est vrai si vous utilisez Spring 2.x, je ne peux pas parler pour d'autres cadres DI.
Donc, ma réponse à l'OP serait (dans tous, sauf l'exemple de code le plus trivial) de:
Cette approche vous donne une belle architecture découplée (et donc flexible et testable) où l'utilisation d'un singleton est un détail d'implémentation facilement réversible (à condition que tous les singletons que vous utilisez soient threadsafe, bien sûr).
TicketNumberer
qui doit avoir une seule instance globale et où vous voulez écrire une classe TicketIssuer
qui contient une ligne de code int ticketNumber = ticketNumberer.nextTicketNumber();
. Dans la pensée singleton traditionnelle, la ligne de code précédente devrait ressembler à quelque chose TicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
. Dans la pensée DI, la classe aurait un constructeur comme public TicketIssuer(TicketNumberer ticketNumberer) { this.ticketNumberer = ticketNumberer; }
.
main
méthode de l'application (ou l'un de ses serviteurs) créerait la dépendance, puis appellerait le constructeur. Essentiellement, l'utilisation d'une variable globale (ou d'une méthode globale) n'est qu'une simple forme du modèle de localisateur de service redouté et peut être remplacée par une injection de dépendance, tout comme toute autre utilisation de ce modèle.
Voyez vraiment pourquoi vous avez besoin d'un singleton avant de l'écrire. Il y a un débat quasi religieux sur leur utilisation que vous pouvez très facilement trébucher si vous google singletons en Java.
Personnellement, j'essaie d'éviter les singletons aussi souvent que possible pour de nombreuses raisons, dont la plupart peuvent être trouvées en recherchant des singletons sur Google. Je pense que très souvent les singletons sont abusés parce qu'ils sont faciles à comprendre par tout le monde, ils sont utilisés comme mécanisme pour obtenir des données "globales" dans une conception OO et ils sont utilisés parce qu'il est facile de contourner la gestion du cycle de vie des objets (ou vraiment penser à comment vous pouvez faire A de l'intérieur B). Regardez des choses comme l'inversion de contrôle (IoC) ou l'injection de dépendance (DI) pour un bon compromis.
Si vous en avez vraiment besoin, wikipedia a un bon exemple d'implémentation correcte d'un singleton.
Voici 3 approches différentes
1) Enum
/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
INSTANCE;
}
2) Double verrouillage / chargement paresseux
/**
* 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) Méthode d'usine statique
/**
* 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;
}
}
J'utilise le Spring Framework pour gérer mes singletons. Il n'applique pas le "singleton-ness" de la classe (ce que vous ne pouvez pas vraiment faire de toute façon si plusieurs chargeurs de classe sont impliqués) mais fournit un moyen très simple de construire et de configurer différentes usines pour créer différents types d'objets.
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;
}
}
Chargement paresseux, thread-safe avec blocage, faible performance en raison de 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;
}
}
Chargement paresseux, thread-safe avec non-blocage, haute performance.
Si vous n'avez pas besoin d'un chargement paresseux, essayez simplement
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();
}
}
Si vous voulez un chargement paresseux et que votre Singleton soit thread-safe, essayez le modèle de double vérification
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();
}
}
Comme le modèle de double vérification n'est pas garanti de fonctionner (en raison d'un problème avec les compilateurs, je n'en sais rien de plus.), Vous pouvez également essayer de synchroniser l'ensemble de la méthode getInstance ou de créer un registre pour tous vos singletons.
volatile
Je dirais Enum singleton
Singleton utilisant enum en Java est généralement un moyen de déclarer enum singleton. Enum singleton peut contenir une variable d'instance et une méthode d'instance. Par souci de simplicité, notez également que si vous utilisez une méthode d'instance, vous devez garantir la sécurité des threads de cette méthode si elle affecte l'état de l'objet.
L'utilisation d'une énumération est très facile à implémenter et ne présente aucun inconvénient concernant les objets sérialisables, qui doivent être contournés dans les autres manières.
/**
* Singleton pattern example using Java Enum
*/
public enum Singleton {
INSTANCE;
public void execute (String arg) {
//perform operation here
}
}
Vous pouvez y accéder par Singleton.INSTANCE
, beaucoup plus facile que d'appeler la getInstance()
méthode sur Singleton.
1.12 Sérialisation des constantes Enum
Les constantes Enum sont sérialisées différemment des objets sérialisables ou externalisables ordinaires. La forme sérialisée d'une constante enum consiste uniquement en son nom; les valeurs de champ de la constante ne sont pas présentes dans le formulaire. Pour sérialiser une constante enum,
ObjectOutputStream
écrit la valeur renvoyée par la méthode name de la constante enum. Pour désérialiser une constante enum,ObjectInputStream
lit le nom de la constante dans le flux; la constante désérialisée est alors obtenue en appelant lajava.lang.Enum.valueOf
méthode, en passant le type d'énumération de la constante avec le nom de la constante reçue comme arguments. Comme d'autres objets sérialisables ou externalisables, les constantes enum peuvent fonctionner comme les cibles de références arrières apparaissant ultérieurement dans le flux de sérialisation.Le processus par lequel les constantes ENUM sont sérialisés ne peut être personnalisé: une classe spécifique
writeObject
,readObject
,readObjectNoData
,writeReplace
, etreadResolve
méthodes définies par les types enum sont ignorés pendant la sérialisation et la désérialisation. De même, les déclarations anyserialPersistentFields
ouserialVersionUID
field sont également ignorées - tous les types d'énumération ont un fixeserialVersionUID
de0L
. Il n'est pas nécessaire de documenter les champs et les données sérialisables pour les types d'énumération, car il n'y a pas de variation dans le type de données envoyées.
Un autre problème avec les Singletons conventionnels est qu'une fois que vous implémentez l' Serializable
interface, ils ne restent plus Singleton car la readObject()
méthode retourne toujours une nouvelle instance comme constructeur en Java. Cela peut être évité en utilisant readResolve()
et en supprimant l'instance nouvellement créée en remplaçant par singleton comme ci-dessous
// readResolve to prevent another instance of Singleton
private Object readResolve(){
return INSTANCE;
}
Cela peut devenir encore plus complexe si votre classe Singleton maintient l'état, car vous devez les rendre transitoires, mais avec Enum Singleton, la sérialisation est garantie par JVM.
Bonne lecture
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");
}
}
Peut-être un peu tard dans le jeu à ce sujet, mais il y a beaucoup de nuances autour de la mise en œuvre d'un singleton. Le modèle de support ne peut pas être utilisé dans de nombreuses situations. Et IMO lors de l'utilisation d'un volatile - vous devez également utiliser une variable locale. Commençons par le début et répétons le problème. Vous verrez ce que je veux dire.
La première tentative pourrait ressembler à ceci:
public class MySingleton {
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
...
}
Ici, nous avons la classe MySingleton qui a un membre statique privé appelé INSTANCE et une méthode statique publique appelée getInstance (). La première fois que getInstance () est appelée, le membre INSTANCE est nul. Le flux tombera alors dans la condition de création et créera une nouvelle instance de la classe MySingleton. Les appels suivants à getInstance () constateront que la variable INSTANCE est déjà définie et ne créeront donc pas une autre instance MySingleton. Cela garantit qu'il n'y a qu'une seule instance de MySingleton qui est partagée entre tous les appelants de getInstance ().
Mais cette implémentation a un problème. Les applications multithread auront une condition de concurrence lors de la création de l'instance unique. Si plusieurs threads d'exécution frappent la méthode getInstance () en même temps (ou environ), ils verront chacun le membre INSTANCE comme nul. Cela entraînera la création d'une nouvelle instance MySingleton par chaque thread et la définition ultérieure du membre INSTANCE.
private static MySingleton INSTANCE;
public static synchronized MySingleton getInstance() {
if (INSTANCE == null) {
INSTANCE = new MySingleton();
}
return INSTANCE;
}
Ici, nous avons utilisé le mot clé synchronized dans la signature de la méthode pour synchroniser la méthode getInstance (). Cela corrigera certainement notre condition de course. Les threads vont maintenant se bloquer et entrer la méthode une par une. Mais cela crée également un problème de performances. Non seulement cette implémentation synchronise la création de l'instance unique, mais elle synchronise tous les appels à getInstance (), y compris les lectures. Les lectures n'ont pas besoin d'être synchronisées car elles renvoient simplement la valeur de INSTANCE. Étant donné que les lectures constitueront la majeure partie de nos appels (rappelez-vous, l'instanciation ne se produit que lors du premier appel), nous subirons un impact de performance inutile en synchronisant la méthode entière.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronize(MySingleton.class) {
INSTANCE = new MySingleton();
}
}
return INSTANCE;
}
Ici, nous avons déplacé la synchronisation de la signature de la méthode vers un bloc synchronisé qui encapsule la création de l'instance MySingleton. Mais cela résout-il notre problème? Eh bien, nous ne bloquons plus les lectures, mais nous avons également fait un pas en arrière. Plusieurs threads frapperont la méthode getInstance () en même temps ou à peu près en même temps et ils verront tous le membre INSTANCE comme nul. Ils frapperont alors le bloc synchronisé où l'on obtiendra le verrou et créera l'instance. Lorsque ce thread quitte le bloc, les autres threads se disputeront le verrou, et un par un, chaque thread tombera dans le bloc et créera une nouvelle instance de notre classe. Nous sommes donc de retour là où nous avons commencé.
private static MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Ici, nous émettons un autre chèque à l'intérieur du bloc. Si le membre INSTANCE a déjà été défini, nous ignorerons l'initialisation. C'est ce qu'on appelle le verrouillage à double vérification.
Cela résout notre problème d'instanciation multiple. Mais encore une fois, notre solution a présenté un autre défi. D'autres threads peuvent ne pas «voir» que le membre INSTANCE a été mis à jour. Cela est dû à la façon dont Java optimise les opérations de mémoire. Les threads copient les valeurs d'origine des variables de la mémoire principale dans le cache du CPU. Les modifications apportées aux valeurs sont ensuite écrites et lues dans ce cache. Il s'agit d'une fonctionnalité de Java conçue pour optimiser les performances. Mais cela crée un problème pour notre implémentation singleton. Un deuxième thread - en cours de traitement par un autre processeur ou noyau, utilisant un cache différent - ne verra pas les modifications apportées par le premier. Cela entraînera le deuxième thread à voir le membre INSTANCE comme null forçant la création d'une nouvelle instance de notre singleton.
private static volatile MySingleton INSTANCE;
public static MySingleton getInstance() {
if (INSTANCE == null) {
synchronized(MySingleton.class) {
if (INSTANCE == null) {
INSTANCE = createInstance();
}
}
}
return INSTANCE;
}
Nous résolvons cela en utilisant le mot-clé volatile sur la déclaration du membre INSTANCE. Cela indiquera au compilateur de toujours lire et d'écrire dans la mémoire principale et non dans le cache du processeur.
Mais ce simple changement a un coût. Parce que nous contournons le cache du processeur, nous prendrons un coup de performance à chaque fois que nous opérons sur le membre INSTANCE volatile - ce que nous faisons 4 fois. Nous vérifions l'existence (1 et 2), définissons la valeur (3), puis retournons la valeur (4). On pourrait soutenir que ce chemin est le cas marginal car nous ne créons l'instance que lors du premier appel de la méthode. Peut-être qu'un succès de performance à la création est tolérable. Mais même notre cas d'utilisation principal, reads, fonctionnera deux fois sur le membre volatil. Une fois pour vérifier l'existence, et encore pour retourner sa valeur.
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;
}
Étant donné que la performance est due à un fonctionnement direct sur le membre volatil, définissons une variable locale à la valeur du volatile et opérons à la place sur la variable locale. Cela diminuera le nombre de fois où nous opérons sur le volatile, récupérant ainsi une partie de nos performances perdues. Notez que nous devons redéfinir notre variable locale lorsque nous entrons dans le bloc synchronisé. Cela garantit qu'il est à jour avec tous les changements survenus pendant que nous attendions le verrou.
J'ai récemment écrit un article à ce sujet. Déconstruire le Singleton . Vous pouvez trouver plus d'informations sur ces exemples et un exemple du modèle "titulaire" ici. Il existe également un exemple concret présentant l'approche volatile à double vérification. J'espère que cela t'aides.
class MySingleton
- peut-être que ça devrait être final
?
BearerToken
instance n'est pas statique car elle fait partie de la BearerTokenFactory
- qui est configurée avec un serveur d'autorisation spécifique. Il peut y avoir de nombreux BearerTokenFactory
objets - chacun ayant son propre «cache» BearerToken
qu'il distribue jusqu'à son expiration. La hasExpired()
méthode sur BeraerToken
est appelée dans la get()
méthode de l'usine pour s'assurer qu'elle ne distribue pas de jeton expiré. S'il expire, un nouveau jeton sera demandé au serveur d'autorisation. Le paragraphe suivant le bloc de code explique cela plus en détail.
Voici comment implémenter un simple 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;
}
}
Voici comment créer correctement votre paresseux 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()
. Mais en effet, si vous n'avez pas d'autres méthodes statiques dans votre classe Singleton
et que vous appelez uniquement, getInstance()
il n'y a pas de réelle différence.
Vous avez besoin de vérifier l' idiome si vous avez besoin de charger la variable d'instance d'une classe paresseusement. Si vous devez charger une variable statique ou un singleton paresseusement, vous avez besoin d'une initialisation sur le support de demande idiome du .
De plus, si le singleton doit être seriliazble, tous les autres champs doivent être transitoires et la méthode readResolve () doit être implémentée afin de maintenir l'invariant de l'objet singleton. Sinon, chaque fois que l'objet est désérialisé, une nouvelle instance de l'objet sera créée. Ce que readResolve () fait est de remplacer le nouvel objet lu par readObject (), ce qui a forcé ce nouvel objet à être récupéré car il n'y a pas de variable s'y référant.
public static final INSTANCE == ....
private Object readResolve() {
return INSTANCE; // original singleton instance.
}
Différentes façons de créer un objet singleton:
Selon Joshua Bloch - Enum serait le meilleur.
vous pouvez également utiliser le verrouillage à double contrôle.
Même une classe statique interne peut être utilisée.
Enum singleton
La façon la plus simple d'implémenter un Singleton qui est thread-safe est d'utiliser un Enum
public enum SingletonEnum {
INSTANCE;
public void doSomething(){
System.out.println("This is a singleton");
}
}
Ce code fonctionne depuis l'introduction d'Enum dans Java 1.5
Verrouillage à double contrôle
Si vous voulez coder un singleton «classique» qui fonctionne dans un environnement multithread (à partir de Java 1.5), vous devez utiliser celui-ci.
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 ;
}
}
Ce n'est pas thread-safe avant 1.5 car l'implémentation du mot-clé volatile était différente.
Chargement précoce de Singleton (fonctionne même avant Java 1.5)
Cette implémentation instancie le singleton lorsque la classe est chargée et assure la sécurité des threads.
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");
}
}
Pour JSE 5.0 et versions ultérieures, adoptez l'approche Enum, sinon utilisez l'approche statique du titulaire singleton ((une approche de chargement paresseux décrite par Bill Pugh). La dernière solution est également thread-safe sans nécessiter de constructions de langage spéciales (c'est-à-dire volatiles ou synchronisées).
Un autre argument souvent utilisé contre les singletons est leurs problèmes de testabilité. Les singletons ne sont pas facilement moquables à des fins de test. Si cela s'avère être un problème, j'aime apporter la légère modification suivante:
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");
}
}
La setInstance
méthode ajoutée permet de définir une implémentation maquette de la classe singleton pendant les tests:
public class SingletonMock extends SingletonImpl {
@Override
public void a() {
System.out.println("Mock Method");
}
}
Cela fonctionne également avec les approches d'initialisation précoce:
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");
}
}
Cela présente l'inconvénient d'exposer également cette fonctionnalité à l'application normale. D'autres développeurs travaillant sur ce code pourraient être tentés d'utiliser la méthode «setInstance» pour modifier altérer une fonction spécifique et ainsi changer le comportement de l'application entière, donc cette méthode devrait contenir au moins un bon avertissement dans son javadoc.
Pourtant, pour la possibilité de tests de maquette (si nécessaire), cette exposition au code peut être un prix acceptable à payer.
classe singleton la plus simple
public class Singleton {
private static Singleton singleInstance = new Singleton();
private Singleton() {}
public static Singleton getSingleInstance() {
return singleInstance;
}
}
Je pense toujours qu'après java 1.5, l'énumération est la meilleure implémentation de singleton disponible car elle garantit également que même dans les environnements multithreads - une seule instance est créée.
public enum Singleton{
INSTANCE;
}
et vous avez terminé !!!
Jetez un oeil à ce post.
Exemples de modèles de conception GoF dans les bibliothèques principales de Java
Dans la section "Singleton" de la meilleure réponse,
Singleton (reconnaissable par les méthodes de création renvoyant la même instance (généralement d'elle-même) à chaque fois)
- java.lang.Runtime # getRuntime ()
- java.awt.Desktop # getDesktop ()
- java.lang.System # getSecurityManager ()
Vous pouvez également apprendre l'exemple de Singleton à partir des classes natives Java elles-mêmes.
Le meilleur motif singleton que j'ai jamais vu utilise l'interface fournisseur.
Voir ci-dessous:
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();
}
}
Parfois, un simple " static Foo foo = new Foo();
" ne suffit pas. Pensez simplement à l'insertion de données de base que vous souhaitez effectuer.
D'un autre côté, vous devrez synchroniser toute méthode qui instancie la variable singleton en tant que telle. La synchronisation n'est pas mauvaise en tant que telle, mais elle peut entraîner des problèmes de performances ou de verrouillage (dans des situations très très rares en utilisant cet exemple. La solution est
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;
}
}
Maintenant, que se passe-t-il? La classe est chargée via le chargeur de classe. Directement après que la classe a été interprétée à partir d'un tableau d'octets, la machine virtuelle exécute le bloc {} - statique . c'est tout le secret: le bloc statique n'est appelé qu'une seule fois, le moment où la classe (nom) donnée du paquet donné est chargée par ce chargeur de classe.
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;
}
}
Comme nous avons ajouté le mot clé Synchronized avant getInstance, nous avons évité la condition de concurrence critique dans le cas où deux threads appellent getInstance en même temps.