130 votes

Java Singleton et Synchronisation

Veuillez clarifier mes questions concernant Singleton et Multithreading :

  • Quelle est la meilleure façon d'implémenter Singleton en Java, dans un environnement multithread ?
  • Que se passe-t-il lorsque plusieurs threads tentent d'accéder à la méthode getInstance() en même temps ?
  • Pouvons-nous rendre getInstance() de Singleton synchronized ?
  • La synchronisation est-elle vraiment nécessaire lorsque l'on utilise des classes Singleton ?

225voto

Jeffrey Points 22209

Oui, c'est nécessaire. Il existe plusieurs méthodes que vous pouvez utiliser pour garantir la sécurité des threads avec une initialisation paresseuse:

Synchronisation draconienne:

private static YourObject instance;

public static synchronized YourObject getInstance() {
    if (instance == null) {
        instance = new YourObject();
    }
    return instance;
}

Cette solution nécessite que chaque fil soit synchronisé alors qu'en réalité seuls les premiers ont besoin de l'être.

Double vérification de la synchronisation:

private static final Object lock = new Object();
private static volatile YourObject instance;

public static YourObject getInstance() {
    YourObject r = instance;
    if (r == null) {
        synchronized (lock) {    // Pendant que nous attendions le verrou, un autre 
            r = instance;        // fil pourrait avoir instancié l'objet.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

Cette solution garantit que seuls les premiers fils qui essaient d'acquérir votre singleton doivent passer par le processus d'acquisition du verrou.

Initialisation sur demande:

private static class InstanceHolder {
    private static final YourObject instance = new YourObject();
}

public static YourObject getInstance() {
    return InstanceHolder.instance;
}

Cette solution exploite les garanties du modèle de mémoire Java concernant l'initialisation de classe pour garantir la sécurité des threads. Chaque classe ne peut être chargée qu'une seule fois, et elle ne le sera que lorsqu'elle est nécessaire. Cela signifie que la première fois que getInstance est appelé, InstanceHolder sera chargé et instance sera créé, et comme cela est contrôlé par des ClassLoaders, aucune synchronisation supplémentaire n'est nécessaire.

24 votes

Attention - soyez prudent avec la synchronisation doublement vérifiée. Cela ne fonctionne pas correctement avec les JVM pré-Java 5 en raison de "problèmes" avec le modèle de mémoire.

3 votes

-1 La synchronisation draconienne et la synchronisation de vérification double de la méthode getInstance() doivent être statiques !

2 votes

@PeterRader Ils n'ont pas besoin d'être statiques, mais cela pourrait avoir plus de sens s'ils l'étaient. Modifié comme demandé.

80voto

Bohemian Points 134107

Ce modèle effectue une initialisation paresseuse thread-safe de l'instance sans synchronisation explicite!

public class MySingleton {

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

     private MySingleton () {}

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

Cela fonctionne car il utilise le chargeur de classe pour effectuer toute la synchronisation gratuitement : la classe MySingleton.Loader est d'abord accédée à l'intérieur de la méthode getInstance(), donc la classe Loader se charge lorsque getInstance() est appelée pour la première fois. De plus, le chargeur de classe garantit que toute initialisation statique est complète avant que vous puissiez accéder à la classe - c'est ce qui vous donne la thread-safety.

C'est comme de la magie.

C'est en fait très similaire au modèle enum de Jhurtado, mais je trouve que le modèle enum est un abus du concept enum (bien que cela fonctionne)

12 votes

La synchronisation est toujours présente, elle est simplement imposée par le JVM au lieu du programmeur.

0 votes

@Jeffrey Tu as raison bien sûr - je tapais tout (voir modifications)

0 votes

Pourquoi ne pas ajouter un qualificatif "final" explicite à la déclaration de INSTANCE ? Il m'a fallu un certain temps pour résoudre le problème sans cela.

22voto

jhurtado Points 2748

Si vous travaillez dans un environnement multithread en Java et que vous devez garantir que tous ces threads accèdent à une seule instance d'une classe, vous pouvez utiliser un Enum. Cela aura l'avantage supplémentaire de vous aider à gérer la sérialisation.

public enum Singleton {
    SINGLE;
    public void myMethod(){  
    }
}

et ensuite il suffit que vos threads utilisent votre instance comme:

Singleton.SINGLE.myMethod();

10voto

Oleksi Points 9596

Oui, vous devez rendre getInstance() synchronisé. Sinon, il peut y avoir une situation où plusieurs instances de la classe peuvent être créées.

Considérez le cas où vous avez deux threads qui appellent getInstance() en même temps. Maintenant, imaginez que T1 exécute juste après la vérification instance == null, puis T2 s'exécute. À ce moment-là, l'instance n'est pas encore créée ou définie, donc T2 passera la vérification et créera l'instance. Maintenant, imaginez que l'exécution revient à T1. Maintenant, le singleton est créé, mais T1 a déjà effectué la vérification! Il procédera à recréer l'objet! Rendre getInstance() synchronisé empêche ce problème.

Il existe quelques façons de rendre les singletons thread-safe, mais rendre getInstance() synchronisé est probablement la plus simple.

0 votes

La mise du code de création d'objet dans un bloc synchronisé aidera-t-elle, au lieu de rendre l'ensemble de la méthode synchronisée ?

0 votes

@RaoG Non. Vous voulez à la fois le contrôle et la création dans le bloc de synchronisation. Vous avez besoin que ces deux opérations se déroulent ensemble sans interruption, sinon la situation que j'ai décrite ci-dessus pourrait se produire.

2voto

Vimsha Points 12671

Vous pouvez également utiliser un bloc de code statique pour instancier l'instance lors du chargement de la classe et éviter les problèmes de synchronisation des threads.

public class MySingleton {

  private static final MySingleton instance;

  static {
     instance = new MySingleton();
  }

  private MySingleton() {
  }

  public static MySingleton getInstance() {
    return instance;
  }

}

0 votes

@Vimsha Quelques autres choses. 1. Vous devriez rendre instance final 2. Vous devriez rendre getInstance() statique.

0 votes

Que feriez-vous si vous vouliez créer un thread dans le singleton.

0 votes

@arun-george utilisez un pool de threads, un pool de threads unique si nécessaire, et entourez-le d'un while(true)-try-catch-throwable si vous voulez vous assurer que votre thread ne meurt jamais, quelle que soit l'erreur.

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