Java Singleton avec une classe interne – qu’est-ce qui garantit la sécurité des threads?

Une manière courante ( 1 , 2 ) d’implémenter un singleton utilise une classe interne avec un membre statique:

public class Singleton { private static class SingletonHolder { public static final Singleton instance = new Singleton(); } public static Singleton getInstance() { return SingletonHolder.instance; } private Singleton() { //... } } 

Cette implémentation est dite paresseusement initialisée et thread-safe. Mais qu’est-ce qui garantit exactement la sécurité de son fil? JLS 17 qui traite des threads et des verrous ne mentionne pas que les champs statiques ont une sorte de relation ” se passe avant” . Comment puis-je être sûr que l’initialisation ne se produira qu’une seule fois et que tous les threads voient la même instance?

Nous devons d’abord comprendre deux points:

L’initialisation statique n’a lieu qu’une fois lors du chargement de la classe

Les champs ayant le modificateur statique dans leur déclaration sont appelés champs statiques ou variables de classe. Ils sont associés à la classe plutôt qu’à aucun object. Chaque instance de la classe partage une variable de classe qui se trouve dans un emplacement fixe en mémoire.

….

L’initialisation d’une classe consiste à exécuter ses initialiseurs statiques et les initialiseurs de champs statiques (variables de classe) déclarés dans la classe

Cela signifie que les initialiseurs statiques ne sont exécutés qu’une seule fois lors de l’initialisation de la classe d’object (l’object de classe réel et non une instance de la classe).

Comme le langage de programmation Java est multithread, l’initialisation d’une classe ou d’une interface nécessite une synchronisation minutieuse puisqu’un autre thread peut essayer d’initialiser la même classe ou interface en même temps.

Pour chaque classe ou interface C , il existe un verrou d’initialisation LC unique. Le mappage de C à LC est laissé à la discrétion de la mise en œuvre de Java Virtual Machine.

Maintenant, en termes simples, lorsque deux threads tentent d’initialiser une instance le premier thread qui acquiert LC est celui qui initialise réellement instnace , et comme il le fait de manière statique, java fournit la promesse que cela ne se produit qu’une fois.

Pour plus d’informations sur le locking d’initialisation, consultez JSL 17

C’est bien décrit dans Java Concurrency in Practice :

L’idiome de classe de détenteur d’initialisation lazy utilise une classe dont le seul but est d’initialiser la ressource. La machine virtuelle Java reporte l’initialisation de la classe ResourceHolder jusqu’à son utilisation effective [JLS 12.4.1]. Etant donné que la ressource est initialisée avec un initialiseur statique, aucune synchronisation supplémentaire n’est nécessaire. Le premier appel à getresource par un thread entraîne le chargement et l’initialisation de ResourceHolder. L’initialisation de la ressource s’effectue à ce moment-là via l’initialiseur statique.

Initialisation statique

Les initialiseurs statiques sont exécutés par la machine virtuelle Java au moment de l’initialisation de la classe, après le chargement de la classe mais avant que la classe ne soit utilisée par un thread. Étant donné que la machine virtuelle Java acquiert un verrou lors de l’initialisation [JLS 12.4.2] et que ce verrou est acquis au moins une fois par chaque thread afin de garantir le chargement de la classe, les écritures en mémoire effectuées lors de l’initialisation statique sont automatiquement visibles par tous les threads. Ainsi, les objects initialisés statiquement ne nécessitent aucune synchronisation explicite, que ce soit pendant la construction ou lors de la référence.