Vous aimez ce que vous lisez sur ce blog ?
Envie d'aller plus loin avec véritable formation d'expertise en Java ?
Venez suivre ma formation Masterclasse Expertise Java !

"Même un développeur experimenté a besoin de continuer à apprendre. Et dans cette formation... j'ai appris beaucoup !" - A.G., Java Champion

Sessions intra-entreprises sur demande : contact[at]mokatech.net.
Inscrivez-vous vite !

Java Quiz #22

Au programme cette semaine, un peu de synchronisation...

Imaginez que dans une classe, vous avez besoin de faire des initialisations, mais que vous ne voulez faire ces initialisations qu'au premier appel.

Vous écrivez alors ceci :

  1. private static Boolean inited = Boolean.FALSE;
  2. ...
  3. public void doJob() {
  4. // On commence par initialiser ce qui doit l'être
  5. synchronized (inited) {
  6. if (!inited) {
  7. init();
  8. inited = Boolean.TRUE;
  9. }
  10. }
  11.  
  12. // On fait le boulot
  13. ...
  14. }
  15. ...

Or ce code peut poser problème. Pourquoi ?

Réponse : le problème vient du fait que l'on fait un synchronized sur une instance de java.lang.Boolean.

Si cette classe est la seule à faire un tel synchronized, aucun problème ne devrait se manifester. Par contre, si jamais une autre classe fait aussi un synchronized sur une instance de java.lang.Boolean, alors il y a un risque élevé de deadlock, parce que les objets Boolean.FALSE et Boolean.TRUE sont partagés pour toute la JVM.

Moralité :
1 - ne jamais poser de verrou sur une variable que l'on modifie dans le bloc synchronisé...
2 - ne jamais poser de verrou sur une variable que l'on n'a pas soi-même instanciée. Ou plus exactement, il faut se méfier des diverses optimisations du langage, qui rendent communes à toute la JVM certaines valeurs :

  • celles de Boolean,
  • celles des classes wrapper (Integer, Byte...) représentant des nombres inférieurs à 128,
  • les chaînes de caractère qui sont placées dans le pool interne
  • etc...

Commentaires

1. Le mercredi 12 novembre 2008, 23:15 par Tom

Vite fait, je dirais que le compilo peut optimiser le code en déplaçant le 'if' en dehors du synchronized. Un peu comme le problème du double checked locking. M'enfin, je dis ça, j'dis rien.

Tom

2. Le jeudi 13 novembre 2008, 11:07 par Bh@Mp0

Je dirai déjà que le "if(!inited)" est incorrect et devrait être "if(!inited.booleanValue())". Ensuite, je pense que le problème vient de la synchronisation sur "inited" seulement qui ne doit pas permettre à l'"init()" d'être thread-safe.

Pour Tom, le compilateur ne peut (normalement) pas réaliser cette opération, du fait du synchronized. Exemple de code qui change tout : 1. if(!inited.booleanValue()) { 2. synchronized(inited) { 3. // something... 4. inited = Boolean.TRUE; 5. } 6. }

Imagine deux threads A et B. A arrive en 1, remarque que le inited est à FALSE, donc entre dans le "if" puis passe la main à B. B arrive en 1, remarque que le inited est à FALSE (pas encore changé donc c'est bon), donc entre dans le "if" aussi. Dès lors, quelle que soit la façon qu'ils ont de se repasser la main, on fera deux fois le "// something...".

3. Le vendredi 14 novembre 2008, 07:36 par le touilleur

La synchro sur une variabLe non finale et static ne marche pas ? Nicolas

4. Le dimanche 16 novembre 2008, 19:34 par Pascal

inited non volatile, non ?

Si :

- un thread A est dans l'init,

- un thread B est au synchronized(inited), l'optimiseur place inited dans un registre du processeur,

- A passe inited à true, relâche le lock ce qui libère B,

- B arrive donc au if(!inited) mais sans relire la nouvelle valeur d'inited et donc re-execute l'init.

5. Le dimanche 16 novembre 2008, 19:56 par Olivier Croisier

Y'a de l'idée :) Mais ça peut être encore plus grave que ça.

6. Le lundi 17 novembre 2008, 20:36 par Pascal

Plus grave ? Un thread bloqué sur le lock alors :) ?

7. Le mardi 18 novembre 2008, 11:52 par HollyDays

A Bh@Mp0

Le "if (!inited)" est tout à fait correct avec Java5, car le compilateur (unboxing oblige) le transforme derechef en "if (!inited.booleanValue())".

A Tom

Si je comprends bien ce que tu proposes, c'est ni plus ni moins qu'un simple-check locking. Déjà que le double check-locking est un anti-pattern (parce que subtilement buggué sur les plate-formes modernes), mais alors un simple-check locking, c'est le bug garanti... Mauvaise idée ! :-)

A Pascal

inited est non volatile, et il n'a pas besoin de l'être : le synchronized se fait sur inited, ce qui garantit que le thread B relit la valeur des variables qui ont été modifiées par un autre thread (dont inited) lorsqu'il rentre dans ce bloc synchronized après avoir acquis le verrou associé (cf. JLS (17)).

8. Le jeudi 20 novembre 2008, 18:07 par fabien29200

Ne serait-ce pas une histoire d'immuabilité ? Si Boolean.TRUE est immuable, tous les appels à Boolean.TRUE seraient synchronisé dans ce cas ?

Ajouter un commentaire

Le code HTML est affiché comme du texte et les adresses web sont automatiquement transformées.