voici ma classe personnalisée pour le motif singleton. Dans ce code, j'utilise un double contrôle de verrouillage comme ci-dessous. En lisant de nombreux articles sur certaines sources, ils disent que le double contrôle est utile car il empêche deux threads simultanés de s'exécuter simultanément pour créer deux objets différents.
public class DoubleCheckLocking {
public static class SearchBox {
private static volatile SearchBox searchBox;
// private constructor
private SearchBox() {}
// static method to get instance
public static SearchBox getInstance() {
if (searchBox == null) { // first time lock
synchronized (SearchBox.class) {
if (searchBox == null) { // second time lock
searchBox = new SearchBox();
}
}
}
return searchBox;
}
}
Je ne comprends toujours pas le code ci-dessus. Quel est le problème, si deux threads exécutent ensemble la même ligne de code lorsque l'instance est null?
if (searchBox == null) {
synchronized (SearchBox.class) {
if (searchBox == null) {
searchBox = new SearchBox();
}
}
}
Quand cela apparaît. les deux threads verront que l'objet est null. alors les deux se synchronisent. et ensuite, ils vérifient à nouveau, et le voient toujours nul. et créer deux objets différents. OOOPS.
S'il vous plaît expliquer pour moi. Qu'est-ce que j'ai mal compris?
Merci :)
Non, puisque vous obtenez le verrou sur le SearchBox.class
, un seul thread entrera dans le bloc synchronisé à la fois. Donc le premier thread entre, puis trouve searchBox
est nul et le crée puis quitte le bloc synchronisé, puis le deuxième thread entre dans le bloc puis il constate que le searchBox
n'est pas nul car le premier thread déjà créé pour ne pas créer une nouvelle instance de searchBox
.
Le modèle à double vérification est utilisé pour éviter d’obtenir le verrouillage chaque fois que le code est exécuté. Si l'appel ne se produit pas simultanément, la première condition échouera et l'exécution du code n'exécutera pas le verrouillage, économisant ainsi des ressources.
Regardons ce code:
1 if (searchBox == null) {
2 synchronized (SearchBox.class) {
3 if (searchBox == null) {
4 searchBox = new SearchBox();
5 }
6 }
Essayons de raisonner à ce sujet. Disons que nous avons deux threads A
et B
et supposons qu'au moins l'un d'entre eux atteigne la ligne 3 et observe searchBox == null
est true
. Deux threads ne peuvent pas être tous les deux à la ligne 3 en même temps à cause du bloc synchronized
. C'est la clé pour comprendre pourquoi le verrouillage à double vérification fonctionne. Donc, il faut le cas que soit A
ou B
soit passé par synchronized
en premier. Sans perte de généralité, disons que ce fil est A
. Puis, en voyant searchBox == null
est vrai, il entrera dans le corps de la déclaration et affectera searchBox
à une nouvelle instance de SearchBox
. Il finira par quitter le bloc synchronized
. Maintenant, ce sera au tour de B
d'entrer: rappelez-vous, B
a été bloqué dans l'attente de la sortie de A
. Maintenant, quand il entrera dans le bloc, il observera searchBox
. Mais A
n'aura quitté que le fait d'avoir défini searchBox
sur une valeur autre que null
. Terminé.
En passant, en Java, le meilleur moyen d'implémenter un singleton est d'utiliser un type enum
à un seul élément. De Effective Java :
Bien que cette approche n’ait pas encore été adoptée à grande échelle, un type d’énumération à un seul élément est le meilleur moyen de mettre en œuvre un singleton.
Ce double verrouillage n’est nécessaire que si vous êtes inquiet au sujet de nombreux threads appelant le singleton simultanément ou du coût d’obtention d’un verrou en général.
Son but est d'éviter toute synchronisation inutile, afin de garder votre code rapide dans un environnement multithread.
Consultez ce lien pour plus d'informations.
Si vous utilisez Java 1,5 ou plus, et que vous utilisez le mot-clé volatile
dans votre mécanisme de double vérification verrouillé, tout fonctionnera correctement. Si vous utilisez le volatile
mot-clé, votre exemple n'est pas cassé selon le même lien que ci-dessus.
if (searchBox == null) { //1
synchronized (SearchBox.class) {
if (searchBox == null) { //2
searchBox = new SearchBox();
}
}
}
}
Donc, fondamentalement, le if
externe est utilisé pour empêcher les verrous redondants - il permet à tous les threads de savoir qu’il existe déjà un objet et qu’ils n’ont pas besoin de verrouiller/de faire quoi que ce soit. Et le if
interne est utilisé pour permettre à un thread simultané de savoir si un autre a déjà créé l'objet ou non.