Qu’est-ce qu’un «object incomplètement construit»?

La page Java Concurrency in Practice de Goetz, page 41, indique comment this référence peut s’échapper pendant la construction. Un exemple “ne fais pas ça”:

 public class ThisEscape { public ThisEscape(EventSource source) { source.registerListener( new EventListener() { public void onEvent(Event e) { doSomething(e); } }); } } 

Ici, c’est “échapper” via le fait que doSomething(e) fait référence à l’instance englobante de ThisEscape . La situation peut être corrigée à l’aide de méthodes fabriques statiques (commencez par construire l’object brut, puis enregistrez l’écouteur) au lieu de constructeurs publics (effectuant tout le travail). Le livre continue:

La publication d’un object à partir de son constructeur peut publier un object incomplètement construit. Cela est vrai même si la publication est la dernière instruction du constructeur. Si this référence s’échappe lors de la construction, l’object est considéré comme non correctement construit.

Je ne comprends pas tout à fait ça. Si la publication est la dernière instruction du constructeur, tout le travail de construction n’a-t-il pas été effectué auparavant? Comment se fait-il que this ne soit pas valide d’ici là? Apparemment, il y a du vaudou après cela, mais quoi?

    La fin d’un constructeur est une place particulière en termes de simultanéité par rapport aux champs finaux. De la section 17.5 de la spécification du langage Java:

    Un object est considéré comme complètement initialisé à la fin de son constructeur. Un thread qui ne peut voir une référence à un object qu’après l’initialisation complète de cet object est assuré de voir les valeurs correctement initialisées pour les champs finaux de cet object.

    Le modèle d’utilisation des champs finaux est simple. Définissez les champs finaux pour un object dans le constructeur de cet object. N’écrivez pas une référence à l’object en cours de construction à un endroit où un autre thread peut le voir avant que le constructeur de l’object ne soit terminé. 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 les champs finaux au moins aussi à jour que les champs finaux.

    En d’autres termes, votre interlocuteur pourrait voir les champs finaux avec leurs valeurs par défaut s’il examine l’object dans un autre thread. Cela ne se produirait pas si l’enregistrement était audible après la fin du constructeur.

    En ce qui concerne ce qui se passe, je soupçonne qu’il existe une barrière de mémoire implicite à la toute fin d’un constructeur, en veillant à ce que tous les threads “voient” les nouvelles données; sans cette barrière de mémoire appliquée, il pourrait y avoir des problèmes.

    Un autre problème se pose lorsque vous sous-classez ThisEscape et que la classe enfant appelle ce constructeur. L’implicite de cette référence dans EventListener aurait un object incomplètement construit.

    Il y a un temps court mais limité entre la fin de registerListener et le retour du constructeur. Un autre thread pourrait utiliser venir à ce moment et essayer d’appeler doSomething (). Si le moteur d’exécution n’est pas revenu directement dans votre code à ce moment-là, l’object pourrait être dans un état non valide.

    Je ne suis pas sûr de Java, mais un exemple auquel je peux penser est celui où, éventuellement, le runtime déplace l’instance avant de revenir à vous.

    C’est une petite chance que je vous accorde.