Le design pattern Singleton (reloaded)

En savoir plus

1 : public class Singleton {
2 : private static _instance_;
3 :
4 :      private Singleton(){}
5 :
6 :      public static Singleton getInstance() {
7 :          if (_instance_ == null) {
8 :              synchronized(Singleton.class) {
9 :                 if(_instance_ == null) {
10 :                  _instance_ = new Singleton();
11 :               }
12 :            }  
13 :       }
14 :        return _instance_;
15 :     }
16 : }
 

Synchronisation restreinte à l'appel au constructeur.
Code © Edis Consulting

 

Nous comprenons bien que ce qui manque à la solution précédente est que, lorsque le Thread2 rentre dans le bloc synchronisé, il ne vérifie pas si '_instance_' est 'null'. Un idiome effectuant la seconde vérification existe et s'appelle le ''double?check locking''. Voir l'implémentation de la méthode 'getInstance()' ci-contre.

Toutes les conditions semblent réunies pour un fonctionnement parfait mais, malheureusement, le modèle mémoire de la JVM ne le permet pas. Le problème réside dans la ligne 10 : la variable '_instance_' peut devenir non 'null' avant l'exécution du constructeur du Singleton ! Afin de mieux comprendre ce qui se passe lors de l'exécution de l'instruction _instance_ = new Singleton(); inspectons le pseudo?code suivant :

ptrMemory = allocateMemory() ;
assignMemory(_instance_, ptrMemory);
callSingletonConstructor(_instance_);

De la mémoire est d'abord allouée pour l'instance du Singleton. La variable '_instance_' est initialisée pour référencer cet espace de mémoire, '_instance_' est alors non 'null' alors que le Singleton n'est pas complètement initialisé. Finalement le constructeur est invoqué pour l'initialisation du Singleton.

En présence de deux 'threads' concurrents nous pourrions avoir le scénario suivant :

1. Thread1 entre dans 'getInstance()' puis dans le bloc synchronisé en ligne 7 car la variable '_instance_' est null ,

2. Thread1 exécute la ligne 10 et rend la variable '_instance_' non 'null'

3. Thread1 est alors préempté par Thread2 avant l'appel du constructeur,

4. Thread 2 vérifie la variable '_instance_,'. Etant devenue non 'null', il retourne une référence sur celle?ci qui est un objet Singleton construit mais partiellement initialisé.

5. Thread2 est préempté par Thread1,

6. Thread1 complète l'initialisation du singleton en exécutant son constructeur et retourne sa référence.


JDN Développeur Envoyer Imprimer Haut de page