:: Enseignements :: ESIPE :: E4INFO :: 2018-2019 :: Collections Concurrentes ::
[LOGO]

Memory model, Volatile


Memory Model et Volatile.

Exercice 1 - Publication safe

  1. Expliquer ce qu'est le problème de publication ?
  2. Le code suivant a un problème de publication, expliquer lequel.
          public class Foo {
            private String value;
            
            public Foo(String value) { 
              this.value = value;
            }
            
            public String getValue() {
              return value;
            }
          }
        

    Indiquer code avec un main suceptible de montrer le problème de publication.
  3. Le code suivant a-t'il un problème de publication ? Si oui comment le corriger ?
          public class Person {
            private String name;
            private final int age;
            
            public Person(String name, int age) { 
              this.name = name;
              this.age = age;
            }
            ...
          }
        
  4. Le code suivant a-t'il un problème de publication ? Si oui comment le corriger ?
          public class Person {
            private String name;
            private volatile int age;
            
            public Person(String name, int age) { 
              this.name = name;
              this.age = age;
            }
            ...
          }
        
  5. Le code suivant a-t'il un problème de publication ? Si oui comment le corriger ?
          public class Person {
            private final String name;
            private final int age;
            
            public Person(String name, int age) { 
              this.name = name;
              this.age = age;
              new Thread(() -> {
                System.out.println(this.name + " " + this.age);
              }).start();
            }
            ...
          }
        
  6. Le code suivant a-t'il un problème de publication ? Si oui comment le corriger ?
          public class Person {
            private final String name;
            private final int age;
            
            public Person(String name, int age) { 
              this.name = name;
              this.age = age;
              new Thread(() -> {
                System.out.println(name + " " + age);
              }).start();
            }
            ...
          }
        

Exercice 2 - SpinLock

On cherche à écrire un simple lock non-réentrant en utilisant les volatiles et la méthode compareAndSet de la classe VarHandle.

  1. Rappeler ce que "réentrant" veux dire.
  2. Expliquer, pour le code ci-dessous, quel est le comportement que l'on attend si la classe est thread-safe.
    Puis expliquer pourquoi le code du Runnable n'est pas thread-safe (même si on déclare counter volatile).

    Vérifier vos affirmations en exécutant le main.
  3. Pour coder le lock, l'idée est d'utiliser un champ boolean pour représenter le jeton que doit prendre un thread pour rentrer dans le lock. Acquérir le lock revient à passer le champs booléen de faux à vrai. Redonner le lock revient à assigner le champ à faux.
    Que doit-on faire si on arrive pas à acquérir le lock ? Quel est le problème ? Pourquoi utiliser Thread.onSpinWait permet de résoudre le problème ? Sachant que les CPUs modernes ont un pipeline, à votre avis, à quoi sert Thread.onSpinWait ?
  4. Implanter les méthodes lock et unlock et vérifier en exécutant le main que le code du Runnable est désormais thread-safe.
  5. Ajouter une méthode tryLock dans SpinLock qui a le même comportement que la méthode tryLock de l'interface java.util.concurrent.locks.Lock.
    Après avoir mis le code du main en commentaire, ajouter un test vérifiant le fonctionnement de tryLock.

Exercice 3 - Reentrant spin lock

On souhaite maintenant écrire un lock réentrant, on propose pour cela l'algorithme suivant
public class ReentrantSpinLock {
  private volatile int lock;
  private volatile Thread ownerThread;
  
  public void lock() {
    // idée de l'algo
    // on récupère la thread courante
    // si lock est == à 0, on utilise un CAS pour le mettre à 1 et
    //   on sauvegarde la thread qui possède le lock dans ownerThread.
    // sinon on regarde si la thread courante n'est pas ownerThread,
    //   si oui alors on incrémente lock.
    //
    // et il faut une boucle pour retenter le CAS après avoir appelé onSpinWait()
  }
  
  public void unlock() {
    // idée de l'algo
    // si la thread courante est != ownerThread, on pète une exception
    // si lock == 1, on remet ownerThread à null
    // on décrémente lock
  }
  
  public static void main(String[] args) throws InterruptedException {
    var runnable = new Runnable() {
      private int counter;
      private final ReentrantSpinLock spinLock = new ReentrantSpinLock();
      
      @Override
      public void run() {
        for(var i = 0; i < 1_000_000; i++) {
          spinLock.lock();
          try {
            spinLock.lock();
            try {
              counter++;
            } finally {
              spinLock.unlock();
            }
          } finally {
            spinLock.unlock();
          }
        }
      }
    };
    var t1 = new Thread(runnable);
    var t2 = new Thread(runnable);
    t1.start();
    t2.start();
    t1.join();
    t2.join();
    System.out.println("counter " + runnable.counter);
  }
}
   

  1. Ecrire la classe ReentrantSpinLock et vérifier que le main fonctionne.
  2. En fait, on peut rendre le code un peu plus efficace en ne déclarant pas ownerThread volatile et en profitant du fait que l'effet d'une lectere/ecriture volatile sur lock a aussi des effets sur la lecture/ecriture des champs au alentour.
    Modifier votre code et vérifier que le main fonctionne toujours.
    Note: la lecture d'un champ par une même thread ne nécessite pas de lecture/ecriture volatile.