Lors d’un entretien d’évaluation en mai dernier pour le compte d’une grande banque, la personne en face de moi me demande la différence entre volatile et synchronized. J’avoue que je n’ai pas répondu tout à fait correctement. Voici donc quelques éléments de réponses, il est toujours bon d’apprendre quelque chose.
Avant tout vous ne serez amené à utilisez synchronized
et volatile
qu’à de rares occasions si vous travaillez sur une bonne vieille application de gestion. Vu la qualité de certaines API comme Google Guava, mon premier conseil est le suivant : soyez humble. Allez voir un peu sur Internet les dernières nouveautés de ces API plutôt que de vous lancer dans du développement d’application parallèle.
Depuis Java 5 la définition formelle du mot clé volatile
a un peu changé. Dans cet article de Brian Goetz de 2004 sur la JSR-133, Brian présente la différence d’approche sur le modèle mémoire de la JVM. Cela me rappelle un article sur le pattern Double-checked locking dont j’ai parlé il y a 5 ans sur le Touilleur Express. En quelques mots, le mot clé volatile
permet de signifier à la JVM qu’une variable sera modifiée par plusieurs Threads. En déclarant une variable volatile, sa valeur ne sera jamais placée dans le cache local à la Thread courante d’exécution. Chaque lecture et chaque écriture passeront forcément par la mémoire partagée entre les Threads. De fait, l’accès à la variable elle-même devient implicitement synchronisé. Et ce, bien plus efficacement qu’avec un marqueur synchronized sur des méthodes.
C’est de la synchronisation implicite, tout du moins vu de l’oeil du programmeur. Les subtilités de la JVM et le modèle mémoire sont un peu plus complexe que cela. Si nous voulions comparer ces 2 opérateurs, nous pourrions dire que synchronized
s’applique uniquement à des Objets alors que volatile
peut s’utiliser aussi sur des types primitifs.
Le tableau suivant tiré de cet article résume les différences entre synchronized
et volatile
:
Characteristic | Synchronized | Volatile |
---|---|---|
Type of variable | Object | Object or primitive |
Null allowed? | No | Yes |
Can block? | Yes | No |
All cached variables synchronized on access? | Yes | From Java 5 onwards |
When synchronization happens | When you explicitly enter/exit a synchronized block | Whenever a volatile variable is accessed. |
Can be used to combined several operations into an atomic operation? | Yes | Pre-Java 5, no. Atomic get-set of volatiles possible in Java 5. |
Le mot clé volatile
ne rend pas l’accès bloquant pour une Thread, comme le fait synchronized
. Ce n’est donc pas une solution pour faire un sémaphore, une barrière, bref des patterns d’inter-lockages de Threads. Un accès à une variable volatile
ne peut pas bloquer une Thread. Alors que synchronized
s’applique à un objet, volatile
s’applique à la référence d’un objet… qui peut être null. C’est bien l’accès à la référence qui est garantis.
Au quotidien vous ne verrez que très rarement finalement le mot clé volatile
. Ce que l’on voit dans les APis par contre, c’est des types comme AtomicInteger ou sinon des décorateurs complexes comme AtomicReference.
Par expérience donc, je vous recommande plutôt de faire confiance à des APIs publiques et open-source plutôt que de vous lancer dans l’utilisation de ces types complexes. Si vous travaillez sur une API, allez-y. Mais si vous travaillez sur un projet que quelqu’un doit pouvoir maintenir facilement, évitez de vous lancer dans l’implémentation de tout ce qui tourne autour de la synchronisation.
Et donc au prochain entretien que j’aurai, si je ne sais pas répondre je pourrai au moins dire : « et vous avez vu sinon l’article sur le blog le Touilleur Express ? Et bien c’est moi qui l’ai écrit »
Références
http://www.javamex.com/tutorials/synchronization_volatile.shtml
http://www.flickr.com/photos/mstcweb/3550301596/sizes/m/in/photostream/ Photo trouvée sur FlickR, libre de droits en licence Commons Creatives, prise par mstcweb.
A propos du Double Check Locking pattern, je vous invite à lire cet article : http://www.nabeelalimemon.com/blog/2010/11/double-check-locking-pattern-is-not-broken-any-more/
Même si personnellement je ne l’utilise pas (par habitude), le DCL pattern avec un « volatile » ne pose plus de problème depuis l’introduction de la JSR 133 (incluse dans le JDK 1.5) .
Certaines subtilités sont aussi à noter pour éviter certains pieges pas facile à débusquer, comme la non atomicité d’opérations comme les increments/decrements presque anodins que sont ++ et –. Une variable volatile ne garantira en aucun cas l’atomicité d’une telle opération sur elle, et cela conduira à des comportements difficle à cerner. Un synchronized lui le pourra, ou l’usage de l’API concurrent(.atomic)
On ne peut pas toujours éviter d’avoir à traiter des cas d’accès concurrents, et ce même sans développer des API. Je ne pense pas donc qu’il faille en décourager l’écriture, mais plutôt en encourager l’apprentissage. Ainsi ces notions seront utilisées aux bons endroits de la bonne façon.
My 2 cts 😉
Completement d’accord avec David… lorsqu’on a à développer une appli avec du multithreading, je vois mal comment on peut s’en sortir si de base on n’a pas saisit et compris ces deux mots clés… (c’est pas évident, j’avoue que je m’y perds).