Java Singleton et synchronisation

117

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 essaient d'accéder getInstance() à la méthode en même temps?
  • Pouvons-nous faire des singleton getInstance() synchronized?
  • La synchronisation est-elle vraiment nécessaire lors de l'utilisation des classes Singleton?
RickDavis
la source

Réponses:

211

Oui il faut. Il existe plusieurs méthodes que vous pouvez utiliser pour assurer la sécurité des threads avec une initialisation différée:

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 thread soit synchronisé alors qu'en réalité seuls les premiers doivent l'être.

Vérifiez 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) {    // While we were waiting for the lock, another 
            r = instance;        // thread may have instantiated the object.
            if (r == null) {  
                r = new YourObject();
                instance = r;
            }
        }
    }
    return r;
}

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

Initialisation à la demande :

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

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

Cette solution tire parti des garanties du modèle de mémoire Java sur l'initialisation de classe pour assurer la sécurité des threads. Chaque classe ne peut être chargée qu'une seule fois et ne sera chargée que lorsque cela sera nécessaire. Cela signifie que la première fois getInstanceest appelée, InstanceHoldersera chargée et instancecréée, et comme elle est contrôlée par ClassLoaders, aucune synchronisation supplémentaire n'est nécessaire.

Jeffrey
la source
23
Attention - soyez prudent avec la synchronisation revérifiée. Il ne fonctionne pas correctement avec les JVM pré-Java 5 en raison de "problèmes" avec le modèle de mémoire.
Stephen C
3
-1 Draconian synchronizationet Double check synchronizationgetInstance () - la méthode doit être statique!
Grim
2
@PeterRader Ils n'ont pas besoin de l'être static, mais cela aurait plus de sens s'ils l'étaient. Modifié comme demandé.
Jeffrey
4
Il n'est pas garanti que votre implémentation du verrouillage vérifié deux fois fonctionne. C'est en fait expliqué dans l'article que vous avez cité pour le verrouillage vérifié. :) Il y a un exemple là-dedans utilisant volatile qui fonctionne correctement pour 1.5 et plus (le verrouillage double vérifié est simplement cassé en dessous de 1.5). L'initialisation à la demande titulaire également citée dans l'article serait probablement une solution plus simple dans votre réponse.
stuckj
2
@MediumOne AFAIK, rn'est pas nécessaire pour l'exactitude. C'est juste une optimisation pour éviter d'accéder au champ volatil, car c'est beaucoup plus cher que d'accéder à une variable locale.
Jeffrey
69

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 faire toute la synchronisation pour vous gratuitement: la classe MySingleton.Loaderest d'abord accédée à l'intérieur de la getInstance()méthode, donc la Loaderclasse se charge lorsqu'elle getInstance()est appelée pour la première fois. De plus, le chargeur de classe garantit que toute l'initialisation statique est terminée avant que vous n'ayez accès à la classe - c'est ce qui vous assure la sécurité des threads.

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)

Bohème
la source
11
La synchronisation est toujours présente, elle est simplement appliquée par la JVM et non par le programmeur.
Jeffrey
@Jeffrey Vous avez raison bien sûr - je tapais tout (voir les modifications)
Bohème
2
Je comprends que cela ne fait aucune différence pour la JVM, je dis simplement que cela a fait une différence pour moi en ce qui concerne le code auto-documenté. Je n'ai jamais vu toutes les majuscules en Java sans le mot-clé "final" avant (ou enum), j'ai un peu de dissonance cognitive. Pour quelqu'un qui programme Java à plein temps, cela ne ferait probablement pas de différence, mais si vous sautez des langages d'avant en arrière, cela aide à être explicite. Idem pour les débutants. Cependant, je suis sûr que l'on peut s'adapter à ce style assez rapidement; tout en majuscules est probablement suffisant. Je n'ai pas l'intention de choisir, j'ai aimé votre message.
Ruby
1
Excellente réponse, même si je n'en ai pas eu une partie. Pouvez-vous préciser "De plus, le chargeur de classe garantit que toute initialisation statique est terminée avant que vous n'ayez accès à la classe - c'est ce qui vous assure la sécurité des threads." , comment cela aide à la sécurité des threads, je suis un peu confus à ce sujet.
gaurav jain
2
@ wz366 en fait, bien que ce ne soit pas nécessaire, je suis d'accord pour des raisons de style (car il est effectivement définitif car aucun autre code ne peut y accéder) finaldevrait être ajouté. Terminé.
Bohème
21

Si vous travaillez sur un environnement multithread en Java et avez besoin de 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(){  
    }
}

puis demandez simplement à vos threads d'utiliser votre instance comme:

Singleton.SINGLE.myMethod();
jhurtado
la source
8

Oui, vous devez effectuer une getInstance()synchronisation. Si ce n'est pas le cas, il peut se produire une situation dans laquelle 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. Imaginez maintenant que T1 s'exécute juste après la instance == nullvérification, puis T2 s'exécute. À ce stade, l'instance n'est ni créée ni définie, donc T2 passera la vérification et créera l'instance. Imaginez maintenant que l'exécution revienne à T1. Maintenant, le singleton est créé, mais T1 a déjà fait la vérification! Il procédera à nouveau à la création de l'objet! Faire getInstance()synchronisé empêche ce problème.

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

Oleksi
la source
Cela vous aidera-t-il en plaçant le code de création d'objet dans un bloc synchronisé, au lieu d'effectuer une synchronisation complète de la méthode?
RickDavis
@RaoG Non. Vous voulez à la fois le contrôle et la création dans le bloc de synchronisation. Vous devez que ces deux opérations se produisent ensemble sans interruption ou la situation que j'ai décrite ci-dessus peut se produire.
Oleksi
7

Enum singleton

Le moyen le plus simple d'implémenter un Singleton qui est thread-safe consiste à utiliser un Enum

public enum SingletonEnum {
  INSTANCE;
  public void doSomething(){
    System.out.println("This is a singleton");
  }
}

Ce code fonctionne depuis l'introduction d'Enum dans Java 1.5

Verrouillage vérifié deux fois

Si vous voulez coder un singleton «classique» qui fonctionne dans un environnement multithread (à partir de Java 1.5), vous devez utiliser celui-ci.

public class Singleton {

  private static volatile Singleton instance = null;

  private Singleton() {
  }

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

Ce n'est pas thread-safe avant 1.5 car l'implémentation du mot-clé volatile était différente.

Chargement anticipé de Singleton (fonctionne même avant Java 1.5)

Cette implémentation instancie le singleton lorsque la classe est chargée et assure la sécurité des threads.

public class Singleton {

  private static final Singleton instance = new Singleton();

  private Singleton() {
  }

  public static Singleton getInstance() {
    return instance;
  }

  public void doSomething(){
    System.out.println("This is a singleton");
  }

}
Dan moldave
la source
2

Vous pouvez également utiliser un bloc de code statique pour instancier l'instance au 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;
  }

}
usha
la source
@Vimsha Quelques autres choses. 1. Vous devez faire la instancefinale 2. Vous devez rendre getInstance()statique.
John Vint
Que feriez-vous si vous souhaitez créer un thread dans le singleton.
Arun George
@ arun-george utilise un pool de threads, un pool de threads unique si nécessaire, et entoure d'un while (true) -try-catch-throwable si vous voulez vous assurer que votre thread ne meurt jamais, quelle que soit l'erreur?
tgkprog
0

Quelle est la meilleure façon d'implémenter Singleton en Java, dans un environnement multithread?

Reportez-vous à cet article pour connaître la meilleure façon d'implémenter Singleton.

Quel est un moyen efficace d'implémenter un modèle de singleton en Java?

Que se passe-t-il lorsque plusieurs threads tentent d'accéder à la méthode getInstance () en même temps?

Cela dépend de la façon dont vous avez implémenté la méthode.Si vous utilisez le double verrouillage sans variable volatile, vous pouvez obtenir un objet Singleton partiellement construit.

Reportez-vous à cette question pour plus de détails:

Pourquoi volatile est-il utilisé dans cet exemple de verrouillage à double vérification

Pouvons-nous synchroniser getInstance () de singleton?

La synchronisation est-elle vraiment nécessaire lors de l'utilisation des classes Singleton?

Non requis si vous implémentez le Singleton de la manière ci-dessous

  1. intitalisation statique
  2. énumération
  3. LazyInitalaization avec Initialization-on-demand_holder_idiom

Reportez-vous à cette question pour plus de détails

Modèle de conception Java Singleton: questions

Ravindra babu
la source
0
public class Elvis { 
   public static final Elvis INSTANCE = new Elvis();
   private Elvis () {...}
 }

Source: Java efficace -> Point 2

Il suggère de l'utiliser, si vous êtes sûr que cette classe restera toujours singleton.

Ashish
la source