3 votes

Un autre thread peut-il voir un objet effectivement immuable dans un état incohérent s'il est publié avec une référence volatile ?

Selon Java Concurrency in Action, si nous avons la classe suivante :

public class Wrapper {
  private int num;

  public Wrapper(int num) {
    this.num = num;
  }

  public void assertCorrectness() {
    if (num != num)
      throw new AssertionError("This is false");
  }
}

et que nous initialisons une instance de cette classe et la publions d'une manière non sécurisée (par le biais d'un simple champ public par exemple), alors la fonction assertCorrectness() pourrait effectivement lancer une AssertionError, si elle est appelée depuis un autre thread. En d'autres termes, cela signifie qu'un autre thread pourrait voir une référence à jour à l'instance, mais l'état de l'instance elle-même pourrait être obsolète (ainsi un thread peut voir qu'un objet existe mais qu'il est dans un état partiellement construit / inconsistant).

D'autre part, il est dit que la publication d'une instance de cette classe par le biais d'une référence volatile est considérée comme sûre. Cependant, j'ai cru comprendre que volatile garantit simplement que chaque thread verra toujours une version à jour d'une référence, mais pas l'état de l'objet référencé. Nous pouvons donc être sûrs que si un thread attribue une nouvelle instance de la classe Wrapper à un champ volatile, tous les autres threads verront que la référence a été mise à jour. Mais y a-t-il un risque qu'ils voient toujours un objet dans un état incohérent / partiellement construit ?

4voto

Kayaman Points 12541

Non, parce que volatile utilisé établit une relation de type "arrive avant". Sans cette relation, divers réarrangements et autres sont autorisés, ce qui rend possible un état incohérent, mais avec cette relation, la JVM doit vous donner le résultat attendu.

Dans ce cas volatile es no utilisé pour les effets de visibilité (les threads voyant des valeurs à jour), mais la publication sécurisée fournie par le happpens-before. Cette fonctionnalité de volatile est souvent oublié lorsque son utilisation est expliquée.

2voto

user18272775 Points 21

La réponse ci-dessus est correct.

Gardez à l'esprit que effectively immutable + safe publication se comporte de manière non intuitive dans certains cas.
Par exemple :

  1. Si

    • au début thread 1 publie l'objet en toute sécurité o a thread 2
    • puis thread 2 publie un objet de manière non sécurisée o a thread 3

    en fin de compte thread 3 peut voir l'objet o dans un état incohérent
    Voir [1] y [2]

  2. également este

Les vrais objets immuables n'ont pas de tels problèmes.

2voto

Eugene Points 6271

El réponse acceptée est simple, mais génial. volatile est rarement expliqué et utilisé pour la publication sécurisée, pourtant il fournit les garanties nécessaires.

JLS en donne l'explication :

Une écriture dans un champ volatile (§8.3.1.4) a lieu avant toute lecture ultérieure de ce champ.

Mais cette règle seule n'est pas suffisante, nous devons un de plus

Une écriture dans une variable volatile v (§8.3.1.4) se synchronise avec toutes les lectures ultérieures de v par n'importe quel thread (où "ultérieures" est défini selon l'ordre de synchronisation).

L'ordre "synchronizes-with" permet de raisonner sur plusieurs threads et leur visibilité.

C'est déjà compliqué, mais on peut l'appliquer à votre exemple. Malheureusement, nous devons définir une règle supplémentaire concernant la cohérence de l'ordre des événements :

a lire voit le dernier écrire dans l'ordre des événements précédents, ou tout autre écrire.


Supposons que vous ayez deux fils : t1 y t2 y t1 crée un Wrapper w = ... (pas volatile ), tandis que t2 les appels qui assertCorrectness . Dans ce cas, vous n'avez pas happens-before de toute sorte.

En tant que tel, t2 selon la cohérence "happens-before" : peut lire toute autre écriture . Puisque c'est écrit num deux fois, il peut lire n'importe quelle valeur dans ces lectures, bien sûr il peut lire des valeurs différentes ; d'où l'échec.


Quand Wrapper w es volatile les choses changent. Tout d'abord, parce que t1 fait un écrire y t2 fait un lire ils établissent une relation de type "happens-before", selon le rapport :

Une écriture dans un champ volatile (§8.3.1.4) a lieu avant toute lecture ultérieure de ce champ.

Bien que, pas évident, t2 doit observez l'écriture (ce mot "subséquent"), pour que le happens-before soit établi. Un peu plus simple :

// t2
if(w != null) {
    ....
}

Quand t2 voit que w n'est pas nulle, cela signifie qu'elle a "observé" l'écriture que t1 a fait, comme cela se passe avant est établi.

Si nous avons établi happens-before, nous avons également établi "synchronizes-with", ou plus simple :

// t1
Wrapper w = new Wrapper();

// t2
if(w != null) {
    w.assertCorrectness();
}

A ce stade ( w.assertCorrectness(); ) tout ce qui s'est passé dans t1 avant publier le w est garantie d'avoir eu lieu et sera visible pour t2 . Cette erreur est donc impossible. cohérence entre les événements et la réalité ici dit que : a lire (lorsque t2 lit w ) verra le dernier écrire (dans l'ordre des événements) : que new Wrapper() .

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X