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.
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.
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 :
Inconvénients :
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.
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; } }
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.
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.
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.
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.
Le mot-clé final est utilisé ici pour empêcher le sous-classement. Marquer la classe Singleton comme finale présente deux avantages principaux :
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.
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 :
Inconvénients :
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.
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; } }
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 :
Inconvénients :
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.
Nous avons examiné trois façons courantes d'implémenter un singleton thread-safe en 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!