Java Language Visualisation des barrières de lecture / écriture lors de l'utilisation de synchronized / volatile


Exemple

Comme nous le savons, nous devons utiliser le mot-clé synchronized pour rendre l'exécution d'une méthode ou d'un bloc exclusive. Mais peu d’entre nous ne sont peut-être pas au courant d’un autre aspect important de l’utilisation de mots clés synchronized et volatile : en plus de créer une unité de code atomique, elle fournit également une barrière en lecture / écriture . Quelle est cette barrière de lecture / écriture? Discutons-en en utilisant un exemple:

class Counter {

  private Integer count = 10;

  public synchronized void incrementCount() {
    count++;
  }

  public Integer getCount() {
    return count;
  }
}

Supposons qu'un thread A appelle incrementCount() puis un autre thread B appelle getCount() . Dans ce scénario, il n'y a aucune garantie que B verra la valeur actualisée du count . Il peut toujours voir count comme 10 , même s'il est également possible qu'il ne voit jamais la valeur mise à jour du count jamais.

Pour comprendre ce comportement, nous devons comprendre comment le modèle de mémoire Java s'intègre à l'architecture matérielle. En Java, chaque thread a sa propre pile de threads. Cette pile contient: la pile des appels de méthode et la variable locale créée dans ce thread. Dans un système multi-core, il est fort possible que deux threads s'exécutent simultanément dans des cœurs distincts. Dans un tel scénario, il est possible qu'une partie de la pile d'un thread se trouve dans le registre / cache d'un core. Si dans un thread, on accède à un objet en utilisant le mot-clé synchronized (ou volatile ), après le blocage synchronized , le thread synchronise sa copie locale de cette variable avec la mémoire principale. Cela crée une barrière de lecture / écriture et s'assure que le thread voit la dernière valeur de cet objet.

Mais dans notre cas, puisque le thread B n'a pas utilisé l' accès synchronisé à count , il est peut - être la valeur du référant count stocké dans le registre et ne peut jamais voir les mises à jour de fil A. Pour vous assurer que B voit dernière valeur de comptage que nous devons faire getCount() synchronisé également.

public synchronized Integer getCount() {
  return count;
}

Désormais, lorsque le thread A est terminé avec le count mise à jour, il déverrouille l'instance Counter , crée en même temps une barrière d'écriture et vide toutes les modifications effectuées à l'intérieur de ce bloc dans la mémoire principale. De même, lorsque le thread B acquiert le verrou sur la même instance de Counter , il entre dans la barrière de lecture et lit la valeur de count dans la mémoire principale et voit toutes les mises à jour.

visibilité

Même effet de visibilité pour volatile lectures / écritures volatile . Toutes les variables mises à jour avant d'écrire dans volatile seront vidées dans la mémoire principale et toutes les lectures après lecture d' volatile variable volatile proviendront de la mémoire principale.