Les garanties “à jour” pour les valeurs des champs finaux de Java s’étendent-elles aux références indirectes?

La spécification du langage Java définit la sémantique des champs finaux dans la section 17.5 :

Le modèle d’utilisation des champs finaux est simple. Définissez les derniers champs d’un object dans le constructeur de cet object. N’écrivez pas de référence à l’object en cours de construction à un endroit où un autre thread peut le voir avant la fin du constructeur de l’object. Si cela est suivi, alors lorsque l’object est vu par un autre thread, celui-ci verra toujours la version correctement construite des champs finaux de cet object. Il verra également les versions de tout object ou tableau référencé par ces champs finaux qui sont au moins aussi à jour que les champs finaux.

Ma question est la suivante: la garantie “à jour” s’étend-elle au contenu des tableaux nesteds et des objects nesteds?

En un mot: si un thread assigne un graphe d’object mutable à un dernier champ d’un object et que le graphe d’object n’est jamais mis à jour, tous les threads peuvent-ils lire ce graphe d’object en toute sécurité via le champ final?

Un exemple de scénario:

  1. Le thread A construit un HashMap de ArrayLists, puis assigne le HashMap au champ final ‘myFinal’ dans une instance de la classe ‘MyClass’
  2. L’unité d’exécution B voit une référence (non synchronisée) à l’instance MyClass et lit ‘myFinal’, puis accède au contenu de l’une des listes Array et en lit le contenu.

Dans ce scénario, les membres de ArrayList tels qu’ils sont vus par le thread B sont-ils garantis au moins aussi à jour qu’ils l’étaient lorsque le constructeur de MyClass a été terminé?

Je souhaite une clarification de la sémantique du modèle de mémoire Java et des spécifications de langage, plutôt que des solutions alternatives telles que la synchronisation. La réponse de mon rêve serait un oui ou un non, avec une référence au texte pertinent.

Mises à jour:

  • Je m’intéresse à la sémantique de Java 1.5 et ultérieure, c’est-à-dire au modèle de mémoire Java mis à jour introduit via JSR 133. La garantie “à jour” sur les champs finaux a été introduite dans cette mise à jour.

Dans ce scénario, les membres de ArrayList tels qu’ils sont vus par le thread B sont-ils garantis au moins aussi à jour qu’ils l’étaient lorsque le constructeur de MyClass a été terminé?

Oui, ils sont.

Un thread est nécessaire pour lire la mémoire lorsqu’il rencontre une référence pour la première fois. Étant donné que la table de hachage est construite, toutes les entrées y sont toutes neuves, les références aux objects sont up-to-date à ce qu’elles étaient lorsque le constructeur a terminé.

Après cette première rencontre, les règles de visibilité habituelles s’appliquent. Ainsi, lorsqu’un autre thread change de champ non final dans les références finales, l’autre thread peut ne pas voir ce changement, mais il verra quand même la référence issue du constructeur.

En réalité, cela signifie que si vous ne modifiez pas la table de hachage finale après le constructeur, son contenu est une constante pour tous les threads.

MODIFIER

Je savais que j’avais déjà vu cette garantie quelque part.

Voici un paragraphe d’intérêt de cet article qui décrit la JSR 133

Sécurité d’initialisation

Le nouveau JMM cherche également à fournir une nouvelle garantie de sécurité d’initialisation: tant qu’un object est correctement construit (c’est-à-dire qu’une référence à cet object n’est pas publiée avant la fin du constructeur), tous les threads verront les valeurs de ses champs finaux définis dans son constructeur, que la synchronisation soit utilisée ou non pour passer la référence d’un thread à un autre. En outre, toutes les variables pouvant être atteintes via un champ final d’un object correctement construit, telles que les champs d’un object référencé par un champ final, sont également garanties d’être également visibles par d’autres threads. Cela signifie que si un dernier champ contient une référence à, par exemple, une LinkedList, en plus que la valeur correcte de la référence soit visible par d’autres threads, le contenu de cette LinkedList au moment de la construction sera visible par d’autres threads sans synchronisation. Le résultat est un renforcement significatif de la signification de final – que les champs finaux peuvent être consultés en toute sécurité sans synchronisation, et que les compilateurs peuvent supposer que les champs finaux ne changeront pas et pourront donc optimiser plusieurs extractions.

Si le constructeur est écrit comme ceci, vous ne devriez pas avoir de problème:

 public class MyClass { public final Map myFinal; public MyClass () { Map localMap = new HashMap(); localMap.put("key", new ArrayList()); this.myFinal = localMap; } } 

En effet, la carte est entièrement initialisée avant d’être affectée à la référence publique. Une fois le constructeur terminé, la carte finale sera mise à jour.