Quel est un moyen efficace d'implémenter un modèle singleton en Java?
java
singleton
design-patterns
Riyaz Mohammed Ibrahim
la source
la source
Réponses:
Utilisez une énumération:
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 ):
Edit: Une partie en ligne de "Java efficace" dit:
la source
Selon l'utilisation, il existe plusieurs réponses «correctes».
Depuis java5, la meilleure façon de le faire est d'utiliser une énumération:
Avant java5, le cas le plus simple est:
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 unprivate static final Foo
champ pour contenir la seule instance et unepublic 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 à:É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.
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.la source
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
Tout va bien sauf son singleton chargé tôt. Permet d'essayer un singleton paresseux
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
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
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éé
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é
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
Qui en interne sera traité comme
C'est ça! Plus de peur de la sérialisation, des threads et du code laid. Les singleton ENUMS sont également initialisés paresseusement .
-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 .
la source
serialVersionUID
de0L
. 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.
la source
getBar()
. (Et s'ilgetBar
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/iq2eayiRThread safe en Java 5+:
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.
la source
Oubliez l' initialisation paresseuse , c'est trop problématique. C'est la solution la plus simple:
la source
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.
la source
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).
la source
TicketNumberer
qui doit avoir une seule instance globale et où vous voulez écrire une classeTicketIssuer
qui contient une ligne de codeint ticketNumber = ticketNumberer.nextTicketNumber();
. Dans la pensée singleton traditionnelle, la ligne de code précédente devrait ressembler à quelque choseTicketNumberer ticketNumberer = TicketNumberer.INSTANCE;
. Dans la pensée DI, la classe aurait un constructeur commepublic 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.
la source
Voici 3 approches différentes
1) Enum
2) Double verrouillage / chargement paresseux
3) Méthode d'usine statique
la source
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.
la source
Wikipedia a quelques exemples de singletons, également en Java. L'implémentation de Java 5 semble assez complète et est compatible avec les threads (verrouillage vérifié deux fois).
la source
Version 1:
Chargement paresseux, thread-safe avec blocage, faible performance en raison de
synchronized
.Version 2:
Chargement paresseux, thread-safe avec non-blocage, haute performance.
la source
Si vous n'avez pas besoin d'un chargement paresseux, essayez simplement
Si vous voulez un chargement paresseux et que votre Singleton soit thread-safe, essayez le modèle de double vérification
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.
la source
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.
Vous pouvez y accéder par
Singleton.INSTANCE
, beaucoup plus facile que d'appeler lagetInstance()
méthode sur Singleton.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 lareadObject()
méthode retourne toujours une nouvelle instance comme constructeur en Java. Cela peut être évité en utilisantreadResolve()
et en supprimant l'instance nouvellement créée en remplaçant par singleton comme ci-dessousCela 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
la source
la source
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:
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.
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.
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é.
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.
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.
É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.
la source
BearerToken instance
dans votre article ne l'est passtatic
? Et c'est quoiresult.hasExpired()
?class MySingleton
- peut-être que ça devrait êtrefinal
?BearerToken
instance n'est pas statique car elle fait partie de laBearerTokenFactory
- qui est configurée avec un serveur d'autorisation spécifique. Il peut y avoir de nombreuxBearerTokenFactory
objets - chacun ayant son propre «cache»BearerToken
qu'il distribue jusqu'à son expiration. LahasExpired()
méthode surBeraerToken
est appelée dans laget()
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
:Voici comment créer correctement votre paresseux
singleton
:la source
getInstance()
. Mais en effet, si vous n'avez pas d'autres méthodes statiques dans votre classeSingleton
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.
la source
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.
la source
Enum singleton
La façon la plus simple d'implémenter un Singleton qui est thread-safe est d'utiliser un Enum
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.
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.
la source
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).
la source
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:
La
setInstance
méthode ajoutée permet de définir une implémentation maquette de la classe singleton pendant les tests:Cela fonctionne également avec les approches d'initialisation précoce:
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.
la source
classe singleton la plus simple
la source
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é !!!
la source
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,
Vous pouvez également apprendre l'exemple de Singleton à partir des classes natives Java elles-mêmes.
la source
Le meilleur motif singleton que j'ai jamais vu utilise l'interface fournisseur.
Voir ci-dessous:
la source
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
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.
la source
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.
la source