web-dev-qa-db-fra.com

Singleton et multithreading en Java

Quelle est la meilleure façon de travailler avec la classe Singleton dans un environnement multithread?

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

  1. Que se passerait-il si aucune synchronisation n'était maintenue?
  2. Est-ce une bonne pratique d'utiliser la méthode synchronizedgetInstance() ou d'utiliser le bloc synchronized dans getInstance().

Veuillez indiquer s'il existe une autre issue.

23
vivekj011

La tâche n'est pas anodine en théorie, étant donné que vous voulez la rendre vraiment sûre pour les threads.

On trouve un très joli article sur la question @ IBM

Obtenir simplement le singleton n'a pas besoin de synchronisation, car il s'agit simplement d'une lecture. Donc, il suffit de synchroniser le réglage de la synchronisation. À moins que deux bandes de roulement n'essaient de créer le singleton au démarrage en même temps, vous devez vous assurer de vérifier si l'instance est définie deux fois (une à l'extérieur et une à l'intérieur de la synchronisation) pour éviter de réinitialiser 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 quelque peu proche de la solution, bien qu'il ne soit de toute façon 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 une manière plus flexible est d'éviter d'utiliser des singletons statiques et d'utiliser un cadre d'injection tel que Spring pour gérer l'instanciation des objets "singleton-ish" (et vous pouvez configurer l'initialisation paresseuse ).

17
Petter Nordlander

Si vous parlez de threadsafe , initialisation paresseuse de singleton , voici un modèle de code sympa à utiliser qui accomplit Initialisation paresseuse 100% threadsafe 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 n'instanciera le singleton que 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 des classes: vous êtes assuré que toute l'initialisation statique s'est terminée avant que la classe ne soit utilisée, et dans ce code, la classe n'est utilisée que dans la 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 Existera pour gérer ces problèmes.

Édité:

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

La différence de base est qu'avec la version de la classe wrapper, l'instance singleton est créée lorsque la classe wrapper est chargée, ce qui est le cas lors du premier appel à la getInstance(), mais avec la version non wrappé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 aucune différence - la différence serait que tout autre sttic l'initialisation aurait terminé avant l'instance est créée lors de l'utilisation de la version encapsulée, mais cela est facilement traité en ayant simplement la variable d'instance statique listée last dans le la source.

Cependant, si vous chargez la classe par nom, l'histoire est assez différente. L'appel de Class.forName(className) sur une classe car l'initialisation statique se produit, donc si la classe singleton à utiliser est une propriété de votre serveur, avec la 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 voici néanmoins un code de travail complet qui démontre ma contention (chacune des classes suivantes est une classe de niveau supérieur):

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);
}

Production:

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

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

36
Bohemian

Vous avez besoin de synchronisation dans getInstance uniquement si vous initialisez votre singleton paresseusement. Si vous pouviez 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 modifiable, vous devrez synchroniser ses méthodes qui accèdent à des informations qui peuvent être modifiées simultanément.

5
dasblinkenlight

La meilleure façon décrite dans effective Java est:

public class Singelton {

    private static final Singelton singleObject = new Singelton();

    public Singelton getInstance(){
        return singleObject;
    }
}

Pas besoin de synchronisation.

2
sorabh solanki

Cette question dépend vraiment de la manière et du moment de la création de votre instance. Si votre méthode getInstance s'initialise paresseusement:

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

return instance

Ensuite, 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 vérification .

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
nsfyn55

Personne n'utilise Enums comme suggéré dans Java efficace?

1
Florian Salihovic

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

0
Shen Yiyang

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

Pour info, dans certains cas, vous devez échanger le singleton avec une autre implémentation. Ensuite, nous devons modifier la classe, ce qui est une violation de Open Close principal.Le problème avec singleton est que vous ne pouvez pas étendre la classe en raison de la présence d'un constructeur privé. Il est donc préférable que le client parle via l'interface.

Implémentation de Singleton avec la classe et l'interface Enum:

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
Jaimin Patel