848 votes

Ce qui est un moyen efficace de mettre en œuvre un pattern singleton en Java ?

Ce qui est un moyen efficace de mettre en œuvre un pattern singleton en Java ?

808voto

Stephen Denne Points 17031

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."

237voto

Roel Spilker Points 2807

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.

125voto

Bno Points 5688

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

95voto

Stu Thompson Points 16599

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.

93voto

Jonathan Points 3229

Oubliez l’initialisation tardive, il est trop problématique. Il s’agit de la solution la plus simple :

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X