Le modèle de mémoire Java décrit la manière dont les threads du langage de programmation Java interagissent via la mémoire. Avec la description de l'exécution monothread du code, le modèle de mémoire fournit la sémantique du langage de programmation Java.
Le modèle de mémoire Java original, développé en 1995, était largement perçu comme défectueux, empêchant de nombreuses optimisations d'exécution et n'offrant pas de garanties suffisamment solides pour la sécurité du code. Il a été mis à jour via le processus de la communauté Java , sous la forme de la demande de spécification Java 133 (JSR-133), qui a pris effet en 2004, pour Tiger (Java 5.0) .
Contexte
Le langage de programmation et la plate-forme Java offrent des fonctionnalités de thread . La synchronisation entre les threads est notoirement difficile pour les développeurs ; cette difficulté est aggravée par le fait que les applications Java peuvent s'exécuter sur une large gamme de processeurs et de systèmes d'exploitation . Pour pouvoir tirer des conclusions sur le comportement d'un programme, les concepteurs de Java ont décidé qu'ils devaient définir clairement les comportements possibles de tous les programmes Java.
Sur les plateformes modernes, le code n'est souvent pas exécuté dans l'ordre dans lequel il a été écrit. Il est réorganisé par le compilateur, le processeur et le sous-système de mémoire pour atteindre des performances maximales. Sur les architectures multiprocesseurs , les processeurs individuels peuvent avoir leurs propres caches locaux qui ne sont pas synchronisés avec la mémoire principale. Il n'est généralement pas souhaitable d'exiger que les threads restent parfaitement synchronisés les uns avec les autres, car cela serait trop coûteux du point de vue des performances. Cela signifie qu'à tout moment, différents threads peuvent voir des valeurs différentes pour les mêmes données partagées.
Dans un environnement monothread, il est facile de raisonner sur l'exécution du code. L'approche classique nécessite que le système implémente une sémantique de type "as-if-serial" pour les threads individuels de manière isolée. Lorsqu'un thread individuel s'exécute, il semblera que toutes les actions effectuées par ce thread se produisent dans l'ordre dans lequel elles apparaissent dans le programme, même si les actions elles-mêmes se produisent dans le désordre.
Si un thread exécute ses instructions dans le désordre, un autre thread peut voir que ces instructions ont été exécutées dans le désordre, même si cela n'affecte pas la sémantique du premier thread. Par exemple, considérons deux threads avec les instructions suivantes, exécutées simultanément, où les variables x et y sont toutes deux initialisées à 0 :
Si aucune réorganisation n'est effectuée et que la lecture de y dans le thread 2 renvoie la valeur 2, alors la lecture suivante de x doit renvoyer la valeur 1, car l'écriture sur x a été effectuée avant l'écriture sur y. Cependant, si les deux écritures sont réorganisées, alors la lecture de y peut renvoyer la valeur 2 et la lecture de x peut renvoyer la valeur 0.
Le modèle de mémoire Java (JMM) définit le comportement autorisé des programmes multithreads et décrit donc quand de telles réorganisations sont possibles. Il impose des contraintes de temps d'exécution sur la relation entre les threads et la mémoire principale afin d'obtenir des applications Java cohérentes et fiables. Ce faisant, il permet de raisonner sur l'exécution du code dans un environnement multithread, même face aux optimisations effectuées par le compilateur dynamique, le(s) processeur(s) et les caches.
Le modèle de mémoire
Pour l'exécution d'un thread unique, les règles sont simples. La spécification du langage Java exige qu'une machine virtuelle Java respecte la sémantique as-if-serial au sein du thread . Le runtime (qui, dans ce cas, fait généralement référence au compilateur dynamique, au processeur et au sous-système de mémoire) est libre d'introduire toute optimisation d'exécution utile tant que le résultat du thread isolé est garanti d'être exactement le même que celui qu'il aurait été si toutes les instructions avaient été exécutées dans l'ordre dans lequel elles se sont produites dans le programme (également appelé ordre du programme).
Le principal inconvénient de cette approche est que la sémantique as-if-serial n'empêche pas les différents threads d'avoir des vues différentes des données. Le modèle de mémoire fournit des indications claires sur les valeurs qui peuvent être renvoyées lors de la lecture des données. Les règles de base impliquent que les actions individuelles peuvent être réorganisées, tant que la sémantique as-if-serial du thread n'est pas violée, et les actions qui impliquent une communication entre les threads, comme l'acquisition ou la libération d'un verrou , garantissent que les actions qui se produisent avant elles sont vues par d'autres threads qui voient leurs effets. Par exemple, tout ce qui se passe avant la libération d'un verrou sera considéré comme ordonné avant et visible pour tout ce qui se passe après une acquisition ultérieure de ce même verrou.
Mathématiquement, il existe un ordre partiel appelé ordre d'occurrence sur toutes les actions effectuées par le programme. L' ordre d'occurrence englobe l'ordre du programme ; si une action se produit avant une autre dans l'ordre du programme, elle se produira avant l'autre dans l' ordre d'occurrence . De plus, les libérations et les acquisitions ultérieures de verrous forment des arêtes dans le graphe d'occurrence. Une lecture est autorisée à renvoyer la valeur d'une écriture si cette écriture est la dernière écriture sur cette variable avant la lecture le long d'un chemin dans l' ordre d'occurrence , ou si l'écriture n'est pas ordonnée par rapport à cette lecture dans l' ordre d'occurrence .
Impact
Le modèle de mémoire Java a été la première tentative de fournir un modèle de mémoire complet pour un langage de programmation populaire. Il a été justifié par la prévalence croissante des systèmes concurrents et parallèles, et par la nécessité de fournir des outils et des technologies avec une sémantique claire pour ces systèmes. Depuis lors, la nécessité d'un modèle de mémoire a été plus largement acceptée, avec une sémantique similaire fournie pour des langages tels que C++ .