Double verrouillage avec Singleton

Verrouillage à double contrôle avec Singleton

1. introduction

Dans ce didacticiel, nous allons parler du modèle de conception de verrouillage vérifié deux fois. Ce modèle réduit le nombre d’acquisitions de verrouillage en vérifiant simplement au préalable la condition de verrouillage. En conséquence, il y a généralement une amélioration des performances.

Examinons plus en détail son fonctionnement.

2. la mise en oeuvre

Pour commencer, considérons un simple singleton avec une synchronisation draconienne:

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

    // private constructor and other methods ...
}

Bien que cette classe soit thread-safe, nous pouvons voir qu'il existe un inconvénient évident en termes de performances: chaque fois que nous voulons obtenir l'instance de notre singleton, nous devons acquérir un verrou potentiellement inutile.

Pour résoudre ce problème,we could instead start by verifying if we need to create the object in the first place and only in that case we would acquire the lock.

Pour aller plus loin, nous voulons effectuer le même contrôle à nouveau dès que nous entrons dans le bloc synchronisé, afin de conserver l'opération atomique:

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

    // private constructor and other methods...
}

Une chose à garder à l'esprit avec ce modèle est quethe field needs to be volatile pour éviter les problèmes d'incohérence du cache. En fait, le modèle de mémoire Java permet la publication d'objets partiellement initialisés, ce qui peut conduire à des bugs subtils.

3. Des alternatives

Même si le double verrouillage peut potentiellement accélérer les choses, il présente au moins deux problèmes:

  • comme il nécessite le mot clévolatile pour fonctionner correctement, il n'est pas compatible avec Java 1.4 et les versions inférieures

  • c'est assez verbeux et cela rend le code difficile à lire

Pour ces raisons, examinons d'autres options sans ces défauts. Toutes les méthodes suivantes délèguent la tâche de synchronisation à la machine virtuelle Java.

3.1. Initialisation précoce

Le moyen le plus simple de sécuriser les threads consiste à intégrer la création d'objet en ligne ou à utiliser un bloc statique équivalent. Cela profite du fait que les champs statiques et les blocs sont initialisés les uns après les autres (Java Language Specification 12.4.2):

public class EarlyInitSingleton {
    private static final EarlyInitSingleton INSTANCE = new EarlyInitSingleton();
    public static EarlyInitSingleton getInstance() {
        return INSTANCE;
    }

     // private constructor and other methods...
}

3.2. Initialisation à la demande

De plus, comme nous l'avons vu dans la référence Spécification de langage Java du paragraphe précédent, l'initialisation d'une classe a lieu la première fois que nous utilisons l'une de ses méthodes ou ses champs, nous pouvons utiliser une classe statique imbriquée pour implémenter l'initialisation différée:

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

     // private constructor and other methods...
}

Dans ce cas, la classeInstanceHolder attribuera le champ la première fois que nous y accédons en invoquantgetInstance.

3.3. Enum Singleton

La dernière solution provient du livreEffective Java (Item 3) de Joshua Block et utilise unenum au lieu d'unclass. Au moment de la rédaction de cet article, cela est considéré comme le moyen le plus concis et le plus sûr d'écrire un singleton:

public enum EnumSingleton {
    INSTANCE;

    // other methods...
}

4. Conclusion

En résumé, cet article rapide est passé en revue le schéma de verrouillage à double vérification, ses limites et certaines alternatives.

En pratique, la verbosité excessive et le manque de compatibilité ascendante rendent ce modèle sujet aux erreurs et nous devons donc l'éviter. Au lieu de cela, nous devrions utiliser une alternative permettant à la JVM de se synchroniser.

Comme toujours, le code de tous les exemples estavailable on GitHub.