Question Qu'est-ce qu'un moyen efficace d'implémenter un pattern singleton en Java?


Qu'est-ce qu'un moyen efficace d'implémenter un pattern singleton en Java?


724
2017-09-16 09:24


origine


Réponses:


Utilisez une énumération:

public enum Foo {
    INSTANCE;
}

Joshua Bloch a expliqué cette approche dans son Java efficace reloaded parler à Google I / O 2008: lien vers la vidéo. Voir aussi les diapositives 30-32 de sa présentation (effective_java_reloaded.pdf):

La bonne façon d'implémenter 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));
    }
}

Modifier: Un partie en ligne de "Java efficace" dit:

"Cette approche est fonctionnellement équivalente à l'approche de terrain public, sauf qu'elle est plus concise, fournit gratuitement le mécanisme de sérialisation, et fournit une garantie absolue contre l'instanciation multiple, même face à des attaques sophistiquées de sérialisation ou de réflexion. encore à être largement adopté, un type enum mono-élément est la meilleure façon de mettre en œuvre un singleton"


716
2017-09-16 11:31



Selon l'usage, il y a plusieurs réponses "correctes".

Depuis java5, la meilleure façon de le faire est d'utiliser une énumération:

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;
    }

    public Object clone() throws CloneNotSupportedException{
        throw new CloneNotSupportedException("Cannot clone instance of this class");
    }
}

Passons en revue le code. D'abord, vous voulez que la classe soit finale. Dans ce cas, j'ai utilisé le final mot-clé pour laisser les utilisateurs savoir qu'il est final. Ensuite, vous devez rendre le constructeur privé pour empêcher les utilisateurs de créer leur propre Foo. Le lancement d'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 pour contenir la seule instance, et un public static Foo getInstance() méthode pour le renvoyer. La spécification Java vérifie que le constructeur n'est appelé que lorsque la classe est utilisée pour la première fois.

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 soit nécessaire, alors et seulement alors vous devez utiliser l'initialisation paresseuse.

Vous pouvez utiliser un private static class 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;
    }
}

Depuis la ligne private static final Foo INSTANCE = new Foo(); est seulement exécutée quand la classe FooLoader est réellement utilisée, ceci prend soin de l'instantiation paresseuse, et est-il garanti sûr de thread.

Lorsque vous voulez é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 si l'objet a été sérialisé lors d'une exécution précédente de votre programme.


218
2017-09-16 15:44



La solution postée par Stu Thompson est valide dans Java5.0 et plus tard. Mais je préférerais ne pas l'utiliser parce que je pense que c'est une erreur.

Il est facile d'oublier la déclaration volatile et difficile à comprendre pourquoi il est nécessaire. Sans le volatile, ce code ne serait plus sûr pour les threads en raison de l'antipatinage de verrouillage à double vérification. Voir plus à ce sujet au paragraphe 16.2.4 de Java Concurrency en pratique. En bref: Ce modèle (antérieur à Java5.0 ou sans l'instruction volatile) peut renvoyer une référence à l'objet Bar qui est (encore) 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, plus facile à lire.

class Bar {
    private static class BarHolder {
        public static Bar bar = new Bar();
    }

    public static Bar getBar() {
        return BarHolder.bar;
    }
}

119
2017-09-16 12:24



Avertissement: Je viens de résumer toutes les réponses impressionnantes et l'ai écrit dans mes mots.


En mettant en œuvre Singleton, nous avons 2 options
1. Chargement paresseux
2. Chargement anticipé

Le chargement paresseux ajoute un peu de temps (beaucoup pour être honnête) donc utilisez-le seulement quand vous avez un très gros objet ou un gros code de construction ET avez aussi d'autres méthodes ou champs statiques accessibles avant qu'une instance soit nécessaire, alors seulement vous devez utiliser l'initialisation paresseux. Sinon, choisir un chargement anticipé est un bon choix.

Le moyen le plus simple d'implémenter 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 est bon sauf son singleton chargé tôt. Essayons singleton paresseux chargé

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 de multiples fils diaboliques qui veulent beaucoup de nombreuses instances de notre héros. Alors, protégeons-le du multi-threads maléfique

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 ce n'est pas suffisant pour protéger le héros, vraiment !!! C'est le meilleur 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;
    }
}

C'est ce qu'on appelle "l'idiome de verrouillage à double vérification". Il est facile d'oublier la déclaration volatile et difficile à comprendre pourquoi il est nécessaire.
Pour plus de détails: http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Maintenant, nous sommes sûrs du fil du mal, mais qu'en est-il de la sérialisation cruelle? Nous devons nous assurer que même si de-serialiaztion 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 renvoyée, même si l'objet a été sérialisé lors d'une exécution précédente de notre programme.

Enfin, nous avons ajouté suffisamment de protection contre les threads et la sérialisation, mais notre code semble volumineux et moche. Permet à notre héros une marque

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 :)
Depuis la ligne private static final Foo INSTANCE = new Foo(); est seulement exécuté quand la classe FooLoader est effectivement utilisé, cela prend soin de l'instanciation paresseuse,

et est-il garanti d'être thread safe.

Et nous sommes venus si loin, voici la meilleure façon de réaliser tout ce que nous avons fait est le meilleur moyen possible

 public enum Foo {
       INSTANCE;
   }

Qui sera traité en interne comme

public class Foo {

    // It will be our sole hero
    private static final Foo INSTANCE = new Foo();
}

Ce n'est plus la peur de la sérialisation, des threads et du code moche. Aussi Les singlets ENUMS sont initialisés paresseusement.

Cette approche est fonctionnellement équivalente à l'approche du terrain public,   sauf qu'il est plus concis, fournit les machines de sérialisation   gratuitement, et fournit une garantie absolue contre plusieurs   instanciation, même face à la sérialisation sophistiquée ou   attaques par réflexion. Bien que cette approche n'ait pas encore été largement adoptée,   un type enum à un seul élément est le meilleur moyen d'implémenter un singleton.

-Joshua Bloch dans "Java efficace"

Maintenant, vous avez peut-être réalisé pourquoi ENUMS est considéré comme le meilleur moyen de mettre en œuvre Singleton et merci pour votre patience :)
Mise à jour sur mon Blog.


119
2018-05-16 06:24



Fil de sécurité dans 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;
    }
}

MODIFIER: Faites attention au volatile modificateur ici. :) Il 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 fait pas prenez soin de cela - il ne fait que sérialiser 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 mieux discutable. Allez lire et voter sa réponse aussi.


93
2017-09-16 09:51



Oublier 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;
    }
}

86
2017-09-16 09:49



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 foncièrement mauvais avec cela, je suppose, mais c'est juste un mécanisme pour exposer des ressources / données globales, alors assurez-vous que c'est le meilleur moyen. En particulier, j'ai trouvé l'injection de dépendance plus utile, surtout si vous utilisez également des tests unitaires car DI vous permet d'utiliser des ressources simulées à des fins de test.


45
2017-09-16 09:33



N'oubliez pas que Singleton n'est qu'un Singleton pour le Classloader qui l'a chargé. Si vous utilisez plusieurs chargeurs (conteneurs), chacun pourrait avoir sa propre version du singleton.


25
2017-09-16 18:12



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 c'est vrai si vous utilisez Spring 2.x, je ne peux pas parler pour d'autres frameworks DI.

Donc, ma réponse à l'OP serait (dans tous les exemples, sauf l'exemple le plus trivial):

  1. Utilisez un cadre DI comme Spring, puis
  2. Faites-en partie dans votre configuration DI, que vos dépendances soient des singletons, des étendues de requêtes, des étendues de session ou autres.

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 les singletons que vous utilisez soient threadsafe, bien sûr).


20
2017-09-16 12:06



Vraiment considérer 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ébucher facilement si vous google singletons en Java.

Personnellement, j'essaie d'éviter les singletons le plus souvent possible pour de nombreuses raisons, dont la plupart peuvent être trouvées en recherchant des singletons sur Google. Je pense que souvent les singletons sont maltraité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. vraiment penser à la façon dont vous pouvez faire A de l'intérieur B). Regardez des choses comme Inversion of Control (IoC) ou Dependency Injection (DI) pour un bon middleground.

Si vous en avez vraiment besoin, Wikipédia a un bon exemple d'implémentation correcte d'un singleton.


19
2017-09-16 09:48



Voici 3 approches différentes

1) Enum

/**
* Singleton pattern example using Java Enumj
*/
public enum EasySingleton{
    INSTANCE;
}

2) Double coché 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;
    }
}

15
2018-02-17 03:46