Double-checked lockingEn génie logiciel, le verrouillage à double test ou double-checked locking est un ancien patron de conception[1]. Considéré aujourd'hui comme un antipattern du fait des problèmes subtils et difficiles à déceler qu'il pose, il a été utilisé dans le passé pour réduire le surcoût d'acquisition du verrou nécessaire à l'initialisation tardive d'un singleton, en commençant par vérifier l'objet du verrou sans précaution avant, ensuite, de poser effectivement le verrou. En JavaPrenons par exemple la classe Java suivante : // Version mono-{{Langue|en|texte=thread}}
class Singleton1 {
private Outil outil = null;
public Outil getOutil() {
if (outil == null) {
outil = new Outil();
}
return outil;
}
// autres attributs et méthodes…
}
Le problème de cette classe est qu'elle ne fonctionne pas en environnement multi-thread. Si deux threads appellent pour la première fois // Version correcte en environnement multi-{{Langue|en|texte=thread}}, mais potentiellement coûteuse
class Singleton2 {
private Outil outil = null;
public synchronized Outil getOutil() {
if (outil == null) {
outil = new Outil();
}
return outil;
}
// autres attributs et méthodes…
}
Ici, le premier appel à Cependant, la synchronisation d'une méthode peut augmenter considérablement le temps de son exécution[2]. L'acquisition et la libération d'un verrou à chaque fois que la méthode est appelée pourrait donc sembler un surcoût inutile. De nombreux programmeurs ont essayé d'optimiser ce code comme suit :
// Version {{Langue|en|texte=multithreaded}} erronée
// Motif "Double-Checked Locking"
class Singleton3 {
private Outil outil = null;
public Outil getOutil() {
if (outil == null) {
synchronized (this) {
if (outil == null) {
outil = new Outil();
}
}
}
return outil;
}
// autres attributs et méthodes…
}
Intuitivement, cet algorithme semble une solution efficace au problème posé. Cependant, il soulève des problèmes subtils et difficiles à tracer. Ainsi, considérons la séquence d'événements suivante :
L'un des dangers du double-checked locking est que, souvent, cela semblera fonctionner. Cela dépend du compilateur, de la manière dont les threads sont ordonnancés par le système d'exploitation, et aussi d'autres mécanismes de gestion de la concurrence d'accès aux données. Reproduire les cas de plantage peut s'avérer d'autant plus difficile qu'ils sont hautement improbables lorsque le code est exécuté au sein d'un débogueur. L'utilisation du double-checked locking doit donc être bannie autant que possible. Néanmoins, JavaSE 5.0 propose une solution à ce problème avec le mot réservé volatile qui garantit que des threads différents gèrent correctement l'accès concurrent à l'instance unique du singleton[3] : // Fonctionne grâce à la nouvelle sémantique du mot-clé volatile
// Ne fonctionne pas avec Java 1.4 ou précédent
class Singleton4 {
private volatile Outil outil = null;
public Outil getOutil() {
if (outil == null) {
synchronized (this) {
if (outil == null) {
outil = new Outil();
}
}
}
return outil;
}
// autres attributs et méthodes…
}
Cependant, cette sécurité d'accès a un prix : l'accès à une variable volatile est moins efficace que l'accès à une variable normale. De nombreuses versions du patron double-checked locking qui n'utilisent pas de synchronisation explicite ou de variable volatile ont été proposées. À part celles qui reposent sur le mot-clé enum ou le motif de support d'initialisation à la demande, toutes se sont révélées incorrectes[4],[5]. Avec Microsoft Visual C++On peut implémenter le double-checked locking avec Visual C++ 2005 si le pointeur vers la ressource est déclaré volatile. Visual C++ 2005 garantit que les variables volatiles se comportent comme des barrières, comme avec JavaSE 5.0, empêchant aussi bien le compilateur que le processeur de réordonnancer les lectures et écritures à ces variables pour les rendre plus efficaces. Les versions précédentes de Visual C++ n'offraient pas cette garantie. Cependant, marquer le pointeur vers la ressource comme volatile pénalise les performances d'une manière ou d'une autre, en particulier si le pointeur est visible ailleurs dans le code : le compilateur le traitera comme une barrière partout où il est utilisé, même lorsque cela n'est pas nécessaire. RéférencesVoir aussiArticles connexesLiens externes
|
Portal di Ensiklopedia Dunia