Ce qui est un moyen efficace de mettre en œuvre un pattern singleton en Java ?
Réponses
Trop de publicités?Utiliser un enum:
public enum Foo {
INSTANCE;
}
Joshua Bloch a expliqué cette approche dans son Effectif Java Reloaded parler à la Google I/O 2008: lien vers la vidéo. Aussi, voir les diapositives 30-32 de sa présentation (effective_java_reloaded.pdf):
La bonne Façon de mettre en Œuvre un Serializable Singleton
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 "Effective Java" dit:
"Cette approche est fonctionnellement équivalent au domaine public, sauf qu'il est plus concis, fournit la sérialisation des machines gratuitement, et offre une solide garantie contre de multiples instanciation, même dans le visage de sophistiqué de sérialisation ou les attaques par réflexion. Bien que cette approche doit encore être largement adopté, un seul élément d'un type enum est la meilleure façon de mettre en œuvre un singleton."
En fonction de l'utilisation, il existe plusieurs "corriger" ses réponses.
Depuis java5 la meilleure façon de le faire est d'utiliser un enum:
public enum Foo {
INSTANCE;
}
Pré 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;
}
}
Reprenons le code. Tout d'abord, vous voulez la classe final. Dans ce cas, j'ai utilisé l' final
mot-clé pour permettre aux utilisateurs de savoir qu'elle est définitive. Ensuite, vous devez faire le constructeur privé pour empêcher les utilisateurs de créer leurs propres Foo. Lancer une exception à partir 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 de détenir la seule instance, et un public static Foo getInstance()
méthode pour le retour. Les spécifications de Java permet de s'assurer que le constructeur n'est appelée que lorsque la classe est d'abord utilisé.
Lorsque vous avez un objet très grand ou lourd code de la construction ET ont aussi d'autres statique accessible méthodes ou des domaines qui pourraient être utilisés avant une instance est nécessaire, alors, et seulement alors vous devez utiliser l'initialisation tardive.
Vous pouvez utiliser un private static class
à la charge de l'instance. Le code devrait ressembler à:
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;
}
}
Depuis la ligne private static final Foo INSTANCE = new Foo();
n'est exécuté que lorsque la classe FooLoader est effectivement utilisé, cela prend en charge les paresseux de l'instanciation, et il est garanti d'être thread-safe.
Quand vous voulez aussi être en mesure de sérialiser vos objets, vous devez vous assurer que la désérialisation de ne pas créer une 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()
sera assurez-vous que la seule instance qui sera renvoyé, même lorsque l'objet est sérialisé dans un précédent exécution de votre programme.
La solution posté par Stu Thompson est valable dans Java5.0 et versions ultérieures. Mais je préfère ne pas l'utiliser parce que je pense que c'est sujette aux erreurs.
Il est facile d'oublier la volatilité de la déclaration et difficiles à comprendre pourquoi il est nécessaire. Sans la volatilité de ce code n'est pas thread-safe plus en raison de la double vérification de verrouillage antipattern. Voir plus à ce sujet dans le paragraphe 16.2.4 de Java de la Simultanéité dans la Pratique. En bref: Ce motif (avant Java5.0 ou sans la volatilité de la déclaration) pourrait renvoyer une référence à la Barre de l'objet qui est (encore) dans un état incorrect.
Ce modèle a été inventé pour l'optimisation de la performance. Mais ce n'est vraiment pas un souci réel plus. La suite paresseux code d'initialisation est rapide et surtout plus facile à lire.
class Bar {
private static class BarHolder {
public static Bar bar = new Bar();
}
public static Bar getBar() {
return BarHolder.bar;
}
}
Les threads 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: attention à l' volatile
modificateur ici. :) C'est important parce que sans elle, les autres threads ne sont pas garantis par le JMM (Java Memory Model) pour voir les modifications apportées à sa valeur. La synchronisation ne pas prendre soin de cela, il ne sérialise l'accès à ce bloc de code.
EDIT 2: @Bno 's réponse les détails de l'approche recommandée par le projet de Loi Pugh (FindBugs) et est défendable mieux. Allez lire et de voter sa réponse.