Le singleton en multithread

Vous connaissez sans doute le design pattern Singleton qui permet de s’assurer de n’avoir qu’une seule instance d’une classe dans un programme.
Dans cet article, je vous propose de découvrir les risques du Singleton dans vos programmes multithread. Vous y découvrirez pourquoi il faut synchroniser l’appel au Singleton et quelles sont les solutions qui marchent et celles qui ne marchent pas. Surtout, vous y trouverez une explication sur le fait que le double-check locking ne marche pas.

Bonne lecture ;)


Version HTML

Version PDF

11 réflexions au sujet de « Le singleton en multithread »

  1. salut,
    actuellement j’utilise le DCL. C’est intéressant ce que j’ai pu lire au dessus. Si je comprends bien, il faut « synchronized » au niveau de la méthode getInstance () malgré les problèmes de performances afin d’éviter les accès concurrents , non ?

  2. La JSR133 a été écrite pour résoudre ces problèmes.
    http://www.jcp.org/en/jsr/detail?id=133
    Le site de la JCP indique une finale release en date du 30 septembre 2004 et une spécification pour J2SE 5.0 JDK.
    Une partie des spécifications qui lui sont liées sont effectivement incorporées dans la version 4 du JDK, mais on voit qu’elle n’est effective que pour la version 5.
    Comme tout le monde ne travaille pas encore avec la derniere JVM. Je pense donc que bien que ce probleme ait été soulevé il y a longtemps, il est encore d’actualité.

  3. Le problème est que l’article de Javaworld date de 2001.
    Cela fait donc 4 ans. Et la gestion de mémoire dans la JVM, ainsi que les compilateurs, a évoluée depuis 2001.
    Je ne suis donc pas sûr que ces problèmes existent encore avec le JDK 1.3, JDK1.4 et JDK1.5 que ce soit ceux de Sun ou IBM.
    D’où ma question.

  4. Je n’ai malheureusement pas de classe de test prouvant cela à coup sur.
    Au cours de la préparation de cet article, je n’ai pas vu non plus de classe démontrant ce phénomène. Il s’agit içi d’un article principalement théorique.
    Le problème avec le Singleton dépendra de la version de la JVM (choix des optimisations), de la plateforme d’exécution (sur certaines machines multiprocesseurs, les caches sont synchronisés naturellement) et sa probabilité de survenue est relativement faible (il faut interrompre le thread au « bon » moment lors de l’exécution du constructeur, c’est à dire le premier appel à getInstance()).
    L’idée de l’article est de démontrer que ce phénomène est susceptible de se produire à cause des spécifications de la JVM et qu’il faut donc en tenir compte.

  5. Personnellement, je base mes dire uniquement sur l’article de JavaWorld que j’ai cité. Personnellement, je n’ai pas eu l’occasion d’observer le problème.
    Mais je trouve que les arguments donnés dans cette article sont tout à fait valable.
    Après, cela ne doit pas être facile d’observer le problème, comme tous les problème de multi-thread. De plus, comme dans ce cas, ca dépend de l’implémentation de la JVM, il se peut très bien qu’avec certaines JVM, ce solution marche.

  6. Effectivement le ThreadLocal n’est pas des plus optimisés… Les premières implémentations de ThreadLocal étaient loin d’être performante.
    http://www.javaworld.com/javaworld/jw-11-2001/jw-1116-dcl.html

    L’initialiseur static reste la meilleur solution, mais il faut pouvoir l’appliquer. Comme le ClassLoader est libre de charger la classe avant le premier appel, si le constructeur du Singleton utilise d’autres variables, il est possible qu’elles ne soient pas initialisées. On est alors obligé de passer par la synchronisation de la méthode getInstance.

  7. Marrant, on a répondu quasiment la même chose en même temps :)
    Sinon, concernant ta solution christophej avec un ThreadLocal je ne sais pas si elle est très optimisée. En effet, si je comprends bien, chaque thread devra rentrer au moins une fois dans le synchronisez.
    Dans le cas où c’est toujours un thread différent qui fait un getInstance, la solution est aussi lente que celle de mettre un synchronisez sur getInstance voir plus lente à cause de l’utilisation de l’objet ThreadLocal. Je me trompe ?

    Dans ce cas, je pense que la solution que tu propose d’avoir un initialiseur static est la meilleur.

  8. Ta première solution Vincent semble ne pas fonctionner. c’est ce qui est expliqué en tout cas dans l’article de JavaWorld http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html. En faite, rien ne garantit que lorsque tu fais intance = privateInstance, ta variable privateInstance est finie d’être initialisée.
    Et je pense que pour les mêmes raisons, la deuxième solution doit ne pas fonctionner non plus.
    En faite, le problème de ces solutions est qu’un des tests (instance==null) n’est pas dans un synchronized. Ainsi, on a plus la garantie d’un accès exclusif à la variable instance. En faite, la variable instance peut très bien ne pas être null mais être toujours en cours d’initialisation.

  9. Bonjour,

    Je suis désolé, mais ces deux solutions en marchent pas….
    Le problème de la variable temporaire est que le compilateur est libre de la faire disparaitre pour optimiser le programme.
    Vous pouvez le voir dans cet article (Listing 2):
    http://www.javaworld.com/javaworld/jw-05-2001/jw-0525-double.html
    Cet article présente aussi une autre solution qui ne marche pas et dont je n’ai pas parlé qui est la variable de garde (Listing 3).

    Je pense également que le second code ne marchera pas car il retombe dans le tort du Double-check Locking :
    La méthode getInstance n’est pas synchronisée. Imaginons que le Thread2 préempte le Thread1 alors qu’il est dans l’appel au constructeur. getInstance n’étant pas synchronisée, Thread2 entre dans la méthode et teste si instance est null. Il est possible que l’espace mémoire de instance soit alloué alors que le constructeur n’est pas exécuté (partie 3.2.3.2 de l’article). On retourne alors une référence sur un objet qui n’est pas complétement initialisé.

  10. Il faudrait penser à rajouter d’autres solutions qui AMHA fonctionne également.

    Voici, par exemple, une variante du DCL qui fonctionne

    public static Singleton getInstance(){
    if (instance==null)
    {
    synchronized (Singleton.class){ //1
    if(instance==null) //2
    Singleton privateInstance=new Singleton(); //3
    intance = privateInstance;
    }
    }
    return instance;
    }

    Cela évite que le Thread2 voit l’instance avant qu’elle ne soit completement initialisée.

    Une autre variante proposée dans un livre de Eyrolles est la suivante:

    synchronized private static Singleton instanciateInstance() {
    if (instance==null) {
    instance = new Singleton();
    }
    }
    public static Singleton getInstance(){
    if (instance==null)
    {
    instance = instanciateInstance();
    }
    return instance;
    }

    qui AMHA fonctionne également correctement.

    Et ces solutions m’ont l’air bien plus élégante que de passer par un ThreadLoal.

Laisser un commentaire