8 votes

Nécessité d'écrire un tableau volatil dans un bloc synchronisé

Question concernant le JMM et la sémantique des champs volatiles qui sont écrits dans un bloc synchronisé, mais lus de manière non synchronisée.

Dans une première version du code ci-dessous, je ne synchronisais pas les accès car cela n'était pas nécessaire pour les besoins antérieurs (et l'utilisation abusive d'une auto-assignation this.cache = this.cache garantissait une sémantique d'écriture volatile). Certaines exigences ont changé, nécessitant une synchronisation pour s'assurer que des mises à jour en double ne sont pas envoyées. La question que je me pose est la suivante : le bloc de synchronisation empêche-t-il d'exiger l'auto-assignation du champ volatile ?

  // Cache of byte[] data by row and column.
  private volatile byte[][][] cache;

  public byte[] getData(int row, int col)
  {
    return cache[row][col];
  }

  public void updateData(int row, int col, byte[] data)
  {
    synchronized(cache)
    {
      if (!Arrays.equals(data,cache[row][col]))
      {
        cache[row][col] = data;

        // Volatile write.
        // The below line is intentional to ensure a volatile write is
        // made to the array, since access via getData is unsynchronized.
        this.cache = this.cache;

        // Notification code removed
        // (mentioning it since it is the reason for synchronizing).
      }
    }
  }

Sans synchronisation, je pense que l'écriture volatile par auto-assignation est techniquement nécessaire (bien que l'IDE la signale comme n'ayant aucun effet). Avec le bloc synchronisé, je pense qu'elle est toujours nécessaire (puisque la lecture est non synchronisée), mais je veux juste confirmer car cela semble ridicule dans le code si elle n'est pas réellement nécessaire. Je ne suis pas sûr qu'il y ait des garanties que je ne connais pas entre la fin d'un bloc synchronisé et une lecture volatile.

4voto

irreputable Points 25577

Oui, vous avez toujours besoin de l'écriture volatile, conformément au modèle de mémoire Java. Il n'y a pas d'ordre de synchronisation entre le déverrouillage et l'écriture volatile. cache à une lecture volatile ultérieure de cache : déverrouiller -> volatileRead ne garantit pas la visibilité. Vous devez soit déverrouiller -> verrouiller ou volatileWrite -> volatileRead .

Cependant, les vraies JVM ont des garanties de mémoire beaucoup plus fortes. En général, les déverrouiller et volatileWrite ont le même effet mémoire (même s'ils portent sur des variables différentes) ; de même que serrure et volatileLecture .

Nous sommes donc confrontés à un dilemme. La recommandation habituelle est de suivre strictement les spécifications. À moins que vous n'ayez une connaissance très large du sujet. Par exemple, un code JDK peut utiliser des astuces qui ne sont pas théoriquement correctes ; mais le code cible une JVM spécifique et l'auteur est un expert.

Le surcoût relatif de l'écriture volatile supplémentaire ne semble pas très important de toute façon.

Votre code est correct et efficace ; cependant, il est en dehors des modèles typiques ; je le modifierais un peu comme suit :

  private final    byte[][][] cacheF = new ...;  // dimensions fixed?
  private volatile byte[][][] cacheV = cacheF;

  public byte[] getData(int row, int col)
  {
    return cacheV[row][col];
  }

  public void updateData(int row, int col, byte[] data)
  {
    synchronized(cacheF)
    {
      if (!Arrays.equals(data,cacheF[row][col]))
      {
        cacheF[row][col] = data;

        cacheV = cacheF; 
      }
    }
  }

3voto

JB Nizet Points 250258

L'assignation de self permet de s'assurer qu'un autre thread lira la référence de tableau qui a été définie, et non une autre référence de tableau. Mais il se peut qu'un thread modifie le tableau pendant qu'un autre le lit.

Les lectures et les écritures dans le tableau doivent être synchronisées. En outre, je ne stockerais pas aveuglément des tableaux dans un cache et ne les renverrais pas de celui-ci. Un tableau est une structure de données mutable, non sûre pour les threads, et n'importe quel thread peut corrompre le cache en modifiant le tableau. Vous devriez envisager de créer des copies défensives et/ou de renvoyer une liste non modifiable plutôt qu'un tableau d'octets.

3voto

John Vint Points 19804

Une écriture d'index dans un tableau volatile n'a en fait aucun effet sur la mémoire. En d'autres termes, si vous avez déjà instancié le tableau, le fait que le champ soit déclaré comme volatile ne vous donnera pas la sémantique mémoire que vous recherchez lors de l'assignation d'éléments dans le tableau.

En d'autres termes

private volatile byte[][]cache = ...;
cache[row][col] = data;

A la même sémantique de mémoire que

private final byte[][]cache = ...;
cache[row][col] = data;

Pour cette raison, vous devez synchroniser toutes les lectures et écritures dans le tableau. Et quand je dis "même sémantique mémoire", je veux dire qu'il n'y a aucune garantie que les threads liront la valeur la plus récente de cache[row][col]

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