Maison  >  Article  >  Java  >  Ne laissez pas votre singleton se briser ! Voici comment le rendre % Thread-Safe en Java

Ne laissez pas votre singleton se briser ! Voici comment le rendre % Thread-Safe en Java

Patricia Arquette
Patricia Arquetteoriginal
2024-11-02 09:55:02285parcourir

Don’t Let Your Singleton Break! Here’s How to Make It % Thread-Safe in Java

Dans cet article, nous explorerons plusieurs façons d'implémenter un singleton thread-safe en Java, notamment l'initialisation hâtive, le verrouillage à double vérification, et l'approche classe statique interne. Nous verrons également pourquoi le mot-clé final est bénéfique pour garantir l'intégrité d'un singleton.

Pourquoi utiliser un Singleton ?

Les

Les singletons sont utiles lorsque vous avez besoin d'exactement une instance d'une classe dans toute l'application. Les cas d'utilisation courants incluent la gestion de ressources partagées telles que la journalisation, la configuration ou les pools de connexions. Un singleton garantit que plusieurs demandes d'accès à une classe partagent la même instance, plutôt que d'en créer de nouvelles.

1. Initialisation hâtive : le singleton le plus simple

Le modèle d'initialisation impatient crée l'instance singleton lorsque la classe est chargée. Ceci est simple et garantit la sécurité des threads car l'instance est créée lorsque la classe est chargée par la JVM.

public final class Singleton {
    // Instance is created at class loading time
    private static final Singleton INSTANCE = new Singleton();

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

Avantages et inconvénients de l'initialisation hâtive

Avantages :

  • Simple et thread-safe par défaut, car la JVM garantit que l'initialisation de la classe est thread-safe.
  • Pas besoin de synchronisation ou de complexité supplémentaire.

Inconvénients :

  • L'instance est créée, qu'elle soit utilisée ou non, ce qui peut entraîner un gaspillage de ressources si le singleton n'est jamais nécessaire.

Quand l'utiliser : une initialisation hâtive est préférable lorsque la classe singleton est légère et que vous êtes certain qu'elle sera utilisée pendant l'exécution de l'application.


2. Initialisation paresseuse avec verrouillage revérifié

Dans les cas où vous souhaitez retarder la création du singleton jusqu'à ce qu'il soit nécessaire (appelé initialisation paresseuse), le verrouillage à double vérification fournit une solution thread-safe. Il utilise une synchronisation minimale et garantit que l'instance est créée uniquement lors du premier accès.

public final class Singleton {  // Marked as final to prevent subclassing

    // volatile ensures visibility and prevents instruction reordering
    private static volatile Singleton instance;

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    public static Singleton getInstance() {
        if (instance == null) {               // First check (no locking)
            synchronized (Singleton.class) {   // Locking
                if (instance == null) {        // Second check (with locking)
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Pourquoi le verrouillage à double vérification fonctionne

  1. Première vérification : La vérification if (instance == null) en dehors du bloc synchronisé nous permet d'éviter le verrouillage à chaque fois que getInstance() est appelé. Cela améliore les performances en contournant le bloc synchronisé pour les futurs appels après l'initialisation.

  2. Bloc synchronisé : Une fois que l'instance est nulle, la saisie du bloc synchronisé garantit qu'un seul thread crée l'instance Singleton. Les autres discussions qui atteignent ce point doivent attendre, évitant ainsi les conditions de concurrence.

  3. Deuxième vérification : à l'intérieur du bloc synchronisé, nous vérifions à nouveau l'instance pour nous assurer qu'aucun autre thread ne l'a initialisée pendant que le thread actuel attendait. Cette double vérification garantit qu'une seule instance Singleton est créée.

Pourquoi volatile est nécessaire

Le mot-clé volatile est essentiel dans le modèle de verrouillage à double vérification pour empêcher la réorganisation des instructions. Sans cela, l'instruction instance = new Singleton(); peut apparaître terminé pour d'autres threads avant d'être complètement initialisé, ce qui entraîne le retour d'une instance partiellement construite. volatile garantit qu'une fois que l'instance n'est pas nulle, elle est entièrement construite et visible par tous les threads.

Pourquoi final est une bonne pratique

Le mot-clé final est utilisé ici pour empêcher le sous-classement. Marquer la classe Singleton comme finale présente deux avantages principaux :

  1. Empêche le sous-classement : En rendant la classe finale, nous empêchons les autres classes de l'étendre. Cela garantit qu'une seule instance de la classe Singleton peut exister, car le sous-classement pourrait conduire à des instances supplémentaires, ce qui briserait le modèle singleton.

  2. Immuabilité des signaux : final sert d'indicateur clair aux autres développeurs que la classe singleton est destinée à être immuable et ne doit pas être étendue. Cela rend le code plus facile à comprendre et à maintenir.

En bref, final renforce l'intégrité du singleton et permet d'éviter un comportement inattendu dû au sous-classement.

Avantages et inconvénients du verrouillage à double vérification

Avantages :

  • L'initialisation paresseuse permet d'économiser des ressources en retardant la création jusqu'à ce que cela soit nécessaire.
  • Surcharge de synchronisation minimale grâce à une double vérification.

Inconvénients :

  • Légèrement plus complexe et nécessite du volatile pour des raisons de sécurité.
  • Peut être excessif pour des besoins singleton plus simples où une initialisation hâtive est suffisante.

Quand l'utiliser : ce modèle est utile lorsque la classe singleton est gourmande en ressources et n'est pas toujours nécessaire, ou lorsque les performances dans un environnement multithread sont un problème.


3. Classe statique interne : une alternative d'initialisation paresseuse plus propre

Une approche alternative thread-safe à l'initialisation paresseuse est le modèle classe statique interne. Cela exploite le mécanisme de chargement de classe de Java pour initialiser l'instance singleton uniquement lorsque cela est nécessaire, sans synchronisation explicite.

public final class Singleton {
    // Instance is created at class loading time
    private static final Singleton INSTANCE = new Singleton();

    // Private constructor prevents instantiation from other classes
    private Singleton() {}

    public static Singleton getInstance() {
        return INSTANCE;
    }
}

Comment fonctionne la classe statique interne

Dans ce modèle, la classe SingletonHelper n'est chargée que lorsque getInstance() est appelée pour la première fois. Cela déclenche l'initialisation d'INSTANCE, garantissant un chargement paresseux sans nécessiter de blocs synchronisés.

Avantages et inconvénients de la classe statique interne

Avantages :

  • Thread-safe sans avoir besoin de blocs volatiles ou synchronisés.
  • Simple et propre, avec une initialisation paresseuse dès la conception.

Inconvénients :

  • Légèrement moins intuitif pour les nouveaux développeurs peu familiers avec les mécanismes de chargement de classes de Java.

Quand l'utiliser : utilisez le modèle de classe statique interne lorsque vous souhaitez une initialisation paresseuse avec un code propre et maintenable. C'est souvent le choix préféré en raison de la simplicité et de la sécurité des threads.


Conclusion

Nous avons examiné trois façons courantes d'implémenter un singleton thread-safe en Java :

  1. Initialisation impatiente : Idéal pour les cas simples où le singleton est léger et sera toujours utilisé.
  2. Verrouillage à double vérification : idéal pour les environnements multithread sensibles aux performances et nécessitant une initialisation paresseuse.
  3. Classe statique interne : une approche propre et sécurisée pour les threads de l'initialisation paresseuse utilisant le comportement de chargement de classe de Java.

Chaque approche a ses atouts et est adaptée à différents scénarios. Essayez-les dans vos propres projets et voyez lequel vous convient le mieux ! Faites-moi savoir dans les commentaires si vous avez une approche préférée ou si vous avez des questions.

Bon codage ! ?‍??‍?

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn