2017-10-09 6 views
7

La lecture des spécifications du langage Java, je trouve cet extrait sur les champs finaux:Si vous affectez un objet à un champ final, les autres threads verront-ils les mises à jour précédentes des champs non-final/non-volatile de cet objet?

Le modèle d'utilisation des champs final est simple: Définissez les champs final pour un objet dans le constructeur de cet objet; et n'écrivez pas une référence à l'objet en cours de construction à un endroit où un autre thread peut le voir avant que le constructeur de l'objet ne soit terminé. Si ce est suivi, alors lorsque l'objet est vu par un autre thread, ce thread verra toujours la version correctement construite des champs finaux de l'objet . Il verra également les versions de n'importe quel objet ou tableau référencé par ces derniers champs qui sont au moins aussi à jour que que les champs finaux sont.

Lien: https://docs.oracle.com/javase/specs/jls/se8/html/jls-17.html#jls-17.5

Ma question est de savoir si "versions" mises à jour moyennes? Cela signifie que les champs non-final/non-volatile d'un objet référencé par un champ final seront également lus à partir de la mémoire principale (pas le cache local) après la construction?


Exemple

Alors disons que thread #1 crée un objectB et définit un de ses non-finales/champs non volatiles.

Ensuite thread #2 ensembles même champ à quelque chose de différent, crée une autre objectA avec un dernier champ défini comme objectB, et met ensuite cette objectA quelque part où thread #1 peut l'obtenir.

thread #1 puis obtient le objectA et voit son champ final comme objectB. Est-il possible pour thread #1 de ne pas voir les changements à objectB faite par thread #2?

Ou voici un code montrant ce que je veux dire:

public class Test { 
    private static final ConcurrentLinkedQueue<A> myAs = new ConcurrentLinkedQueue<>(); 
    private static long timer = System.nanoTime() + 3000000000L; // 3 seconds into the future 

    public static void main(String... args) { 
     B myB = new B("thread #1"); // Set in thread 1 

     new Thread(() -> { 
      myB.setString("thread #2"); // Set in thread 2 
      myAs.add(new A(myB)); 
     }).start(); 

     for(long i = 0; i < x; i = System.nanoTime()) {} // Busy-wait for about 3 seconds 

     System.out.println(myAs.poll().getB().getString()); // Print out value 
    } 

    public static class A { 
     private final B b; 

     public A(B b) { 
      this.b = b; 
     } 

     public B getB() { 
      return b; 
     } 
    } 

    public static class B { 
     private String s = null; 

     public B(String s) { 
      this.s = s; 
     } 

     public String getString() { 
      return s; 
     } 

     public void setString(String s) { 
      this.s = s; 
     } 
    } 
} 

Le code semble lire les valeurs mises à jour, mais je ne suis pas sûr que ce soit juste pas de chance au hasard.

+3

Assez bien pour la première question. – lexicore

Répondre

1

"Est-il possible pour thread #1 de ne pas voir les changements à objectB faite par thread #2?"

Oui. Parce que thread #1 peut mettre en cache la valeur.

Citation de la spécification signifie qu'il y a happened before entre l'affectation de la valeur du champ final et la publication de l'objet.

1

Ce qui est intéressant et je pense que je l'ai fait lire à ce sujet il y a un certain temps ... Voici un exemple (si je me souviens bien l'exemple):

static class Holder { 

    private final String[] names; 

    static Holder newHolder; 

    public Holder() { 
     super(); 
     names = new String[3]; 
     names[0] = "first"; 
     names[1] = "last"; 
    } 

    public void newObject() { 
     newHolder = new Holder(); 
     newHolder.names[2] = "oneMore"; 
    } 

    public void readObject() { 
     System.out.println(Arrays.toString(newHolder.names)); 
    } 

} 

Supposons que vous avez deux fils impliqués ici : ThreadA et ThreadB.Et maintenant, supposons également que ThreadA invoque newObject; quand c'est fait ThreadB invoque readObject.

Il n'y a absolument aucune garantie que ThreadB affichera first, last, oneMore; il est seulement garanti que first et last seront présents à coup sûr.

Ce btw est parfaitement compréhensible une fois que vous pensez en termes de MemoryBarriers qui sont utilisés dans le cas de final fields used inside constructor.

Dans la mise en œuvre actuelle, ce fait ressemble à ceci:

public Holder() { 
    super(); 
    names = new String[3]; 
    names[0] = "first"; 
    names[1] = "last"; 
} 
// [StoreStore] 
// [LoadStore] 

il y a deux obstacles qui sont insérés à la fin de l'constrcutor qui empêchent d'autres se produisent les lectures et les magasins.