J'ai quelques questions concernant le modèle de singleton décrit ici: http://msdn.Microsoft.com/en-us/library/ff650316.aspx
Le code suivant est un extrait de l'article:
using System;
public sealed class Singleton
{
private static volatile Singleton instance;
private static object syncRoot = new object();
private Singleton() {}
public static Singleton Instance
{
get
{
if (instance == null)
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
}
return instance;
}
}
}
Plus précisément, dans l'exemple ci-dessus, est-il nécessaire de comparer l'instance à null deux fois, avant et après le verrouillage? Est-ce nécessaire? Pourquoi ne pas commencer par verrouiller et faire la comparaison?
Y at-il un problème pour simplifier à la suivante?
public static Singleton Instance
{
get
{
lock (syncRoot)
{
if (instance == null)
instance = new Singleton();
}
return instance;
}
}
La réalisation du verrou est-elle chère?
Effectuer le verrouillage est terriblement coûteux par rapport à la vérification du pointeur simple instance != null
.
Le modèle que vous voyez ici s'appelle verrouillage vérifié . Son but est d'éviter l'opération de verrouillage coûteuse qui ne sera nécessaire qu'une seule fois (lors du premier accès au singleton). L'implémentation est telle parce qu'elle doit également s'assurer que lors de l'initialisation du singleton, il n'y aura pas de bugs résultant des conditions de concurrence des threads.
Pensez-y de cette façon: un contrôle null
nu (sans lock
) ne vous garantira une réponse utilisable correcte que lorsque cette réponse est "oui, l'objet est déjà construit". Mais si la réponse est "pas encore construit", alors vous n’avez pas assez d’informations car vous vouliez vraiment savoir que c’est "qu’il n’a pas encore été construit et qu’aucun autre thread n’a l’intention de le construire bientôt". Vous utilisez donc la vérification externe comme un test initial très rapide et vous initiez la procédure appropriée, sans bug mais "coûteuse" (verrouiller puis vérifier) uniquement si la réponse est "non".
L'implémentation ci-dessus est suffisante dans la plupart des cas, mais c'est une bonne idée de lire l'article de Jon Skeet sur les singletons en C # qui évalue également d'autres alternatives.
La version paresseuse:
public sealed class Singleton
{
static readonly Lazy<Singleton> lazy = new Lazy<Singleton>(() => new Singleton());
private Singleton() { }
public static Singleton Instance => lazy.Value;
}
Nécessite .NET 4 et C # 6.0 (VS2015) ou une version plus récente.
Réalisation d'un verrou: Assez bon marché (toujours plus cher qu'un test nul).
Exécuter un verrou quand un autre thread l'a: Vous obtenez le coût de tout ce qu'ils ont à faire pendant le verrouillage, ajouté à votre temps.
Exécution d'un verrou lorsqu'un autre thread le possède et que des dizaines d'autres threads l'attendent également: Crippling.
Pour des raisons de performances, vous souhaitez toujours disposer des verrous souhaités par un autre thread, pour la période la plus courte possible.
Bien sûr, il est plus facile de raisonner sur les verrous "larges" que sur les verrous étroits, il est donc utile de commencer par les élargir et de les optimiser au besoin, mais il existe des cas d'expérience et de familiarité dans lesquels un modèle plus étroit correspond au modèle.
(Incidemment, si vous pouvez simplement utiliser private static volatile Singleton instance = new Singleton()
ou si vous ne pouvez pas simplement utiliser des singletons mais utiliser une classe statique à la place, les deux solutions sont meilleures en ce qui concerne ces problèmes).
La raison est la performance. Si instance != null
(ce qui sera toujours le cas sauf la toute première fois), il n’est pas nécessaire de faire une lock
coûteuse: deux threads accédant simultanément au singleton initialisé seraient synchronisés de manière inutile.
Dans presque tous les cas (c'est-à-dire: tous les cas sauf le tout premier), instance
ne sera pas nul. L'obtention d'un verrou est plus coûteuse qu'une simple vérification. Par conséquent, vérifier une fois la valeur de instance
avant le verrouillage est une optimisation agréable et gratuite.
Ce modèle est appelé verrouillage à double vérification: http://en.wikipedia.org/wiki/Double-checked_locking
Jeffrey Richter recommande ce qui suit:
public sealed class Singleton
{
private static readonly Object s_lock = new Object();
private static Singleton instance = null;
private Singleton()
{
}
public static Singleton Instance
{
get
{
if(instance != null) return instance;
Monitor.Enter(s_lock);
Singleton temp = new Singleton();
Interlocked.Exchange(ref instance, temp);
Monitor.Exit(s_lock);
return instance;
}
}
}
Vous pouvez créer avec impatience l'instance Singleton sécurisée pour les threads, en fonction des besoins de votre application, il s'agit d'un code succinct, bien que je préfère la version paresseuse de @ andasa.
public sealed class Singleton
{
private static readonly Singleton instance = new Singleton();
private Singleton() { }
public static Singleton Instance()
{
return instance;
}
}
Une autre version de Singleton où la ligne de code suivante crée l'instance Singleton au moment du démarrage de l'application.
private static readonly Singleton singleInstance = new Singleton();
Ici, CLR (Common Language Runtime) se chargera de l’initialisation de l’objet et de la sécurité des threads. Cela signifie que nous n'aurons pas besoin d'écrire explicitement du code pour gérer la sécurité des threads dans un environnement multithread.
"Le modèle Eager de chargement dans un modèle singleton n'est en aucun cas un processus dans lequel nous devons initialiser l'objet singleton au moment du démarrage de l'application plutôt que sur demande et le garder prêt en mémoire pour pouvoir l'utiliser à l'avenir."
public sealed class Singleton
{
private static int counter = 0;
private Singleton()
{
counter++;
Console.WriteLine("Counter Value " + counter.ToString());
}
private static readonly Singleton singleInstance = new Singleton();
public static Singleton GetInstance
{
get
{
return singleInstance;
}
}
public void PrintDetails(string message)
{
Console.WriteLine(message);
}
}
de main:
static void Main(string[] args)
{
Parallel.Invoke(
() => PrintTeacherDetails(),
() => PrintStudentdetails()
);
Console.ReadLine();
}
private static void PrintTeacherDetails()
{
Singleton fromTeacher = Singleton.GetInstance;
fromTeacher.PrintDetails("From Teacher");
}
private static void PrintStudentdetails()
{
Singleton fromStudent = Singleton.GetInstance;
fromStudent.PrintDetails("From Student");
}