Singleton et Multithreading en Java

Quelle est la manière préférée de travailler avec la classe Singleton dans un environnement multithread?

Supposons que si j'ai 3 threads, et tous essaient d'accéder à la méthode getInstance() de la classe singleton en même temps -

  1. Que se passerait - il si aucune synchronisation n'est maintenue?
  2. Est-il une bonne pratique à utiliser synchronized getInstance() la méthode ou l'utilisation de synchronized bloc à l'intérieur de getInstance().

Veuillez indiquer s'il existe une autre issue.

21
demandé sur didierc 2012-06-17 18:59:37

8 réponses

La tâche est en théorie non triviale, étant donné que vous voulez la rendre vraiment sûre.

Un très bel article sur la question se trouve @ IBM

Juste obtenir le singleton n'a pas besoin de synchronisation, car c'est juste une lecture. Donc, il suffit de synchroniser le réglage de la synchronisation ferait l'affaire. À moins que deux bandes de roulement n'essaient de créer le singleton au démarrage en même temps, vous devez vérifier si l'instance est définie deux fois (une à l'extérieur et une à l'intérieur de la synchronisation) pour éviter la réinitialisation l'instance dans le pire des cas.

Ensuite, vous devrez peut-être prendre en compte la façon dont les compilateurs JIT gèrent les Écritures hors service. Ce code sera un peu proche de la solution, bien que ne sera pas sûr à 100%:

public static Singleton getInstance() {
    if (instance == null) {
        synchronized(Singleton.class) {      
            Singleton inst = instance;         
            if (inst == null) {
                synchronized(Singleton.class) {  
                    instance = new Singleton();               
                }
            }
        }
    }
    return instance;
}

Donc, vous devriez peut-être recourir à quelque chose de moins paresseux:

class Singleton {
    private static Singleton instance = new Singleton();

    private Singleton() { }

    public static Singleton getInstance() {
        return instance;
    }
}

Ou, un peu plus gonflé, mais d'une manière plus souple est d'éviter d'utiliser statique singletons et utiliser une injection cadre comme Printemps pour gérer l'instanciation de objets" singleton-ish " (et vous pouvez configurer l'initialisation paresseuse).

15
répondu Petter Nordlander 2017-01-17 13:41:55

Si vous parlez de threadsafe, initialisation paresseuse du singleton , Voici un modèle de code cool à utiliser qui accomplit 100% threadsafe initialisation paresseuse sans code de synchronisation :

public class MySingleton {

     private static class MyWrapper {
         static MySingleton INSTANCE = new MySingleton();
     }

     private MySingleton () {}

     public static MySingleton getInstance() {
         return MyWrapper.INSTANCE;
     }
}

Cela instanciera le singleton uniquement lorsque getInstance() est appelé, et c'est 100% threadsafe! C'est un classique.

Cela fonctionne parce que le chargeur de classe A sa propre synchronisation pour gérer l'initialisation statique de classes: vous êtes assuré que toute l'initialisation statique est terminée avant que la classe soit utilisée, et dans ce code, la classe est uniquement utilisée dans la méthode getInstance(), donc c'est lorsque la classe chargée charge la classe interne.

En passant, j'attends avec impatience le jour où une annotation @Singleton existe qui gère de tels problèmes.

Édité:

Un mécréant particulier a affirmé que la classe wrapper "ne fait rien". Voici la preuve que fait matière, mais dans des circonstances particulières.

La différence fondamentale est qu'avec la version de la classe wrapper, l'instance singleton est créée lorsque la classe wrapper est chargée, ce qui, lors du premier appel, est getInstance(), mais avec la version non enveloppée-c'est-à-dire une simple initialisation statique-l'instance est créée lorsque la classe principale est chargée.

Si vous n'avez qu'une simple invocation de la méthode getInstance(), alors il n'y a presque {[27] } aucune différence - la différence serait que allother l'initialisation sttic aurait terminé avant que l'instance soit créée lors de l'utilisation de la version encapsulée, mais cela est facilement résolu en ayant simplement la variable d'instance statique listée last dans la source.

Cependant, si vous chargez la classe par name , l'histoire est assez différente. Appeler Class.forName(className) sur une classe pour que l'initialisation statique se produise, donc si la classe singleton à utiliser est une propriété de votre serveur, avec le version simple l'instance statique sera créée lorsque Class.forName() est appelée, pas lorsque getInstance() est appelée. J'admets que c'est un peu artificiel, car vous devez utiliser la réflexion pour obtenir l'instance, mais néanmoins voici un code de travail complet qui démontre ma contention (chacune des classes suivantes est une classe de premier niveau):

public abstract class BaseSingleton {
    private long createdAt = System.currentTimeMillis();

    public String toString() {
        return getClass().getSimpleName() + " was created " + (System.currentTimeMillis() - createdAt) + " ms ago";
    }
}

public class EagerSingleton extends BaseSingleton {

    private static final EagerSingleton INSTANCE = new EagerSingleton();

    public static EagerSingleton getInstance() {
        return INSTANCE;
    }
}

public class LazySingleton extends BaseSingleton {
    private static class Loader {
        static final LazySingleton INSTANCE = new LazySingleton();
    }

    public static LazySingleton getInstance() {
        return Loader.INSTANCE;
    }
}

Et le principal:

public static void main(String[] args) throws Exception {
    // Load the class - assume the name comes from a system property etc
    Class<? extends BaseSingleton> lazyClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.LazySingleton");
    Class<? extends BaseSingleton> eagerClazz = (Class<? extends BaseSingleton>) Class.forName("com.mypackage.EagerSingleton");

    Thread.sleep(1000); // Introduce some delay between loading class and calling getInstance()

    // Invoke the getInstace method on the class
    BaseSingleton lazySingleton = (BaseSingleton) lazyClazz.getMethod("getInstance").invoke(lazyClazz);
    BaseSingleton eagerSingleton = (BaseSingleton) eagerClazz.getMethod("getInstance").invoke(eagerClazz);

    System.out.println(lazySingleton);
    System.out.println(eagerSingleton);
}

Sortie:

LazySingleton was created 0 ms ago
EagerSingleton was created 1001 ms ago

Comme vous pouvez le voir, l'implémentation simple et non encapsulée est créée lorsque Class.forName() est appelé, qui peut être avant l'initialisation statique est prête à être exécutée.

34
répondu Bohemian 2013-10-15 20:24:04

Vous avez besoin d'une synchronisation à l'intérieur de {[0] } uniquement si vous initialisez votre singleton paresseusement. Si vous pouvez créer une instance avant le démarrage des threads, vous pouvez supprimer la synchronisation dans le getter, car la référence devient immuable. Bien sûr, si l'objet singleton lui-même est mutable, vous devez synchroniser ses méthodes qui accèdent aux informations qui peuvent être modifiées simultanément.

5
répondu dasblinkenlight 2012-06-17 15:04:37

Cette question dépend vraiment de comment et quand votre instance est créée. Si votre méthode getInstance s'initialise paresseusement:

if(instance == null){
  instance = new Instance();
}

return instance

, Alors vous devez synchroniser ou vous pourriez vous retrouver avec plusieurs instances. Ce problème est généralement traité dans les discussions sur verrouillage à double contrôle .

Sinon si vous créez une instance statique à l'avant

private static Instance INSTANCE = new Instance();

Alors aucune synchronisation de la méthode getInstance() n'est nécessaire.

2
répondu nsfyn55 2012-06-17 15:17:45

Personne n'utilise les énumérations comme suggéré dans Java efficace ?

1
répondu Florian Salihovic 2012-06-17 15:42:25

La meilleure façon comme décrit dans java efficace est:

public class Singelton {

    private static final Singelton singleObject = new Singelton();

    public Singelton getInstance(){
        return singleObject;
    }
}

Pas besoin de synchronisation.

1
répondu sorabh solanki 2012-11-06 07:41:59

Si vous êtes sûr que votre runtime java est en utilisant le nouveau JMM (modèle de mémoire Java, probablement plus récent que 5.0) , double check lock est très bien, mais ajoutez un volatile devant l'instance. Sinon, vous feriez mieux d'utiliser la classe interne statique comme Bohemian a dit, ou Enum dans "Java efficace" comme l'a dit Florian Salihovic.

0
répondu Shen Yiyang 2012-06-18 09:57:13

Pour simplifier, je pense que l'utilisation de la classe enum est une meilleure façon. Nous n'avons pas besoin de faire une synchronisation. Java par construction, assurez-vous toujours qu'il n'y a qu'une seule constante créée, peu importe le nombre de threads essayant d'y accéder.

Pour info, dans certains cas, vous devez échanger singleton avec d'autres implémentations. Ensuite, nous devons modifier la classe, ce qui est une violation du principe Open Close.Le problème avec singleton est que vous ne pouvez pas étendre la classe en raison d'un constructeur privé. Si, c'est une meilleure pratique que le client parle via l'interface.

Implémentation de Singleton avec la classe enum et L'Interface:

Client.java

public class Client{
    public static void main(String args[]){
        SingletonIface instance = EnumSingleton.INSTANCE;
        instance.operationOnInstance("1");
    }
}

SingletonIface.java

public interface SingletonIface {
    public void operationOnInstance(String newState);
}

EnumSingleton.java

public enum EnumSingleton implements SingletonIface{
INSTANCE;

@Override
public void operationOnInstance(String newState) {
    System.out.println("I am Enum based Singleton");
    }
}
0
répondu Jaimin Patel 2017-11-02 21:11:46