95 votes

Pourquoi le volatile est utilisé dans le verrouillage à double vérification

De La tête d'abord design patterns book, le modèle singleton avec un verrouillage doublement vérifié a été implémenté comme ci-dessous :

public class Singleton {
    private volatile static Singleton instance;
    private Singleton() {}
    public static Singleton getInstance() {
        if (instance == null) {
            synchronized (Singleton.class) {
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

Je ne comprends pas pourquoi volatile est utilisé. N'est pas volatile va à l'encontre de l'objectif de l'utilisation d'un verrouillage à double vérification, à savoir la performance ?

7 votes

Je pensais que le verrouillage à double vérification était cassé, quelqu'un l'a réparé ?

4 votes

Pour ce que ça vaut, j'ai trouvé que Head First design patterns était un livre horrible pour apprendre. En y repensant, il est parfaitement logique maintenant que j'ai appris les modèles ailleurs, mais pour apprendre sans connaître les modèles, il n'a vraiment pas servi son objectif. Mais il est très populaire, alors peut-être que c'était juste moi qui était dense :-)

0 votes

@DavidHeffernan J'ai vu cet exemple utilisé comme la seule façon de faire confiance à la jvm pour faire la DCL.

76voto

Tim Bender Points 11611

Une bonne ressource pour comprendre pourquoi volatile est nécessaire provient de la JCIP livre. Wikipedia a un explication satisfaisante de ce matériel également.

Le vrai problème est que Thread A peut attribuer un espace mémoire pour instance avant d'avoir fini de construire instance . Thread B verra cette affectation et essaiera de l'utiliser. Il en résulte que Thread B échoue parce qu'elle utilise une version partiellement construite de instance .

1 votes

OK, il semble qu'une nouvelle implémentation de volatile ait réglé les problèmes de mémoire avec DCL. Ce que je ne comprends toujours pas, c'est l'implication en termes de performances de l'utilisation de volatile ici. D'après ce que j'ai lu, volatile est à peu près aussi lent que synchronisé, alors pourquoi ne pas simplement synchroniser l'appel complet de la méthode getInstance() ?

6 votes

@toc777 volatile est plus lent qu'un classement habituel. Si vous recherchez la performance, optez pour un motif à classeur. volatile est ici simplement pour montrer que il y a un moyen pour faire fonctionner le modèle brisé. C'est plus un défi de codage qu'un vrai problème.

0 votes

@alf explique beaucoup de choses, merci. Ils ne l'ont pas du tout précisé dans le livre.

30voto

Ravindra babu Points 5571

Comme cité par @irreputable, le volatile n'est pas cher. Même si elle est chère, la cohérence doit être privilégiée par rapport aux performances.

Il existe une autre méthode propre et élégante pour les singletons paresseux.

public final class Singleton {
    private Singleton() {}
    public static Singleton getInstance() {
        return LazyHolder.INSTANCE;
    }
    private static class LazyHolder {
        private static final Singleton INSTANCE = new Singleton();
    }
}

Article source : Initialisation sur demande du titulaire_idiom de wikipedia

En génie logiciel, l'idiome du titulaire de l'initialisation à la demande (design pattern) est un singleton chargé paresseusement. Dans toutes les versions de Java, cet idiome permet une initialisation paresseuse sûre, hautement concurrente et performante.

Puisque la classe n'a pas de static à initialiser, l'initialisation se termine trivialement.

La définition de la classe statique LazyHolder à l'intérieur de celui-ci n'est pas initialisé jusqu'à ce que la JVM détermine que LazyHolder doit être exécuté.

La classe statique LazyHolder n'est exécutée que lorsque la méthode statique getInstance est invoquée sur la classe Singleton, et la première fois que cela se produit, la JVM va charger et initialiser la classe LazyHolder classe.

Cette solution est à l'abri des threads sans nécessiter de constructions spéciales du langage (c'est-à-dire volatile o synchronized ).

16voto

alf Points 5130

Il n'y a pas de verrouillage doublement vérifié pour les performances. C'est un brisé modèle.

Laisser les émotions de côté, volatile est ici parce que sans lui, au moment où le second fil passe instance == null le premier fil pourrait ne pas être construit new Singleton() encore : personne ne promet que la création de l'objet se passe avant affectation à instance pour tout autre thread que celui qui crée réellement l'objet.

volatile établit à son tour se passe avant entre les lectures et les écritures, et corrige le modèle cassé.

Si vous recherchez la performance, utilisez plutôt la classe statique interne du titulaire.

1 votes

Bonjour @alfSelon la définition de happens-before, après qu'un thread libère le verrou, un autre thread acquiert le verrou, puis ce dernier peut voir le changement précédent. Si c'est le cas, je ne pense pas que le mot clé volatile soit nécessaire. Pouvez-vous l'expliquer plus en détail

0 votes

Mais il n'y a pas d'acquisition de verrou dans le cas où le deuxième fil ne touche que le if extérieur, donc pas de commande.

0 votes

Bonjour @alf, Essayez-vous de dire que lorsque le premier thread crée l'instance à l'intérieur d'un bloc synchronisé, cette instance peut encore être nulle pour le second thread à cause des manques de cache et il l'instance à nouveau si l'instance n'est pas volatile ? pouvez-vous clarifier ?

3voto

Chris Points 1084

Regardez :

http://www.cs.umd.edu/~pugh/java/memoryModel/DoubleCheckedLocking.html

Et

http://en.wikipedia.org/wiki/Double-checked_locking

(plus précisément le deuxième, au milieu de la page). Il contient un exemple qui est le portrait craché de ce que vous avez posté et l'explique.

2voto

corsiKa Points 39442

Si vous ne l'aviez pas, un deuxième thread pourrait entrer dans le bloc synchronisé après que le premier l'ait mis à null, et votre cache local penserait toujours qu'il est null.

La première n'a pas pour but d'être correcte (si c'était le cas, vous avez raison de dire que cela irait à l'encontre du but recherché) mais plutôt d'être optimisée.

1 votes

Selon la définition de happens-before, après qu'un thread ait libéré le verrou, un autre thread acquiert le verrou, alors ce dernier peut voir le changement précédent. Dans ce cas, je ne pense pas que le mot clé volatile soit nécessaire. Pouvez-vous l'expliquer plus en détail

0 votes

@QinDongLiang Si la variable n'est pas volatile, le second thread peut utiliser une valeur en cache sur sa propre pile. Le fait qu'elle soit volatile l'oblige à retourner à la source pour obtenir la bonne valeur. Bien sûr, il doit le faire à chaque accès et peut donc avoir un impact sur les performances (mais soyons honnêtes, à moins qu'il ne soit dans une boucle super critique, ce n'est probablement pas la pire chose dans votre système...).

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X