Lock-free : volatile et Atomic*

Volatiles (à plumes ?)

Quels sont les affichages possibles du code ci-dessous :

public class Duck {
    private volatile long a = 1;
    private volatile long b = 1;

    public void foo() {
        a = 2;
        b = -1;
    }

    public static void main(String[] args) {
        var duck = new Duck();
        Thread.ofPlatform().start(() -> {
            System.out.println("b = " + duck.b);
            System.out.println("a = " + duck.a);
        });
        Thread.ofPlatform().start(duck::foo);
    }
} 

Le code ci-dessous, adapté du code de l'exercice 2 du TD 2, est problématique :

public class StopThreadBug {
  private boolean stop;

  public void runCounter() {
    var localCounter = 0;
    for(;;) {
      if (stop) {
        break;
      }
      localCounter++;
    }
    System.out.println(localCounter);
  }

  public void stop() {
    stop = true;
  }

  public static void main(String[] args) throws InterruptedException {
    var bogus = new StopThreadBug();
    var thread = Thread.ofPlatform().start(bogus::runCounter);
    Thread.sleep(100);
    bogus.stop();
    thread.join();
  }
}

Rappelez rapidement où est la data-race et pourquoi on peut observer que le programme ne s'arrête jamais.

Rendez la classe thread-safe sans utiliser de mécanisme de verrou. Quelle propriété garantit que le programme s'arrête ?

Reprenons la classe HonorBoard de l'exercice 2 de la séance 3. Cette fois-ci, les champs de la classe sont volatile.

public class HonorBoard {
  private volatile String firstName;
  private volatile String lastName;
  
  public void set(String firstName, String lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }
  
  @Override
  public String toString() {
    return firstName + ' ' + lastName;
  }
  
  public static void main(String[] args) {
    var board = new HonorBoard();
    Thread.ofPlatform().start(() -> {
      for(;;) {
        board.set("Mickey", "Mouse");
      }
    });
    
    Thread.ofPlatform().start(() -> {
      for(;;) {
        board.set("Donald", "Duck");
      }
    });
    
    Thread.ofPlatform().start(() -> {
      for(;;) {
        System.out.println(board);
      }
    });
  }
}

Est-il toujours possible de voir des affichages de Mickey Duck ou Donald Mouse ?

Rendre la classe thread-safe en utilisant un seul champ volatile.

Générateur aléatoire

On souhaite modifier la classe RandomNumberGenerator.java pour la rendre thread-safe sans utiliser ni section critique ni verrou (lock-free donc).

Expliquer comment fonctionne un générateur pseudo-aléatoire et pourquoi l'implantation ci-dessous n'est pas thread-safe.

public class RandomNumberGenerator {
  private long x;
  
  public RandomNumberGenerator(long seed) {
    if (seed == 0) {
      throw new IllegalArgumentException("seed == 0");
    }
    x = seed;
  }
  
  public long next() {  // Marsaglia's XorShift
    x ^= x >>> 12;
    x ^= x << 25;
    x ^= x >>> 27;
    return x * 2685821657736338717L;
  }
  
  public static void main(String[] args) {
    RandomNumberGenerator rng = new RandomNumberGenerator(1);
    for(var i = 0; i < 5_000; i++) {
      System.out.println(rng.next());
    }
  }
}

Utiliser la classe AtomicLong et sa méthode compareAndSet pour obtenir une implantation lock-free du générateur pseudo-aléatoire.

Simplifiez votre code en utilisant la méthode updateAndGet de la classe AtomicLong.

Liste chaînée (lock-free)

Dans cet exercice, on veut faire une implantation thread-safe d'une liste chaînée avec insertion et suppression en tête n'utilisant ni section critique, ni verrou.

Le code ci-dessous est une implantation qui n'est pas thread-safe.

public class LinkedList<E> {

    private record Link<E>(E value, Link<E> next) {
        private Link {
            Objects.requireNonNull(value);
        }
    }

    private Link<E> head;

    /**
     * Add the non-null value at the start of the list
     *
     * @param value
     */
    public void addFirst(E value) {
        Objects.requireNonNull(value);
        head = new Link<>(value, head);
    }

    /**
     * applies the consumer the elements of the list in order
     *
     * @param consumer
     */
    public void forEach(Consumer<? super E> consumer) {
        Objects.requireNonNull(consumer);
        for (var current = head; current != null; current = current.next) {
            consumer.accept(current.value);
        }
    }

    public static void main(String[] args) {
        var list = new LinkedList<String>();
        list.addFirst("Noel");
        list.addFirst("papa");
        list.addFirst("petit");
        list.forEach(System.out::println);
    }

}    

Dans une classe LinkedListLockFree, implémenter une version thread-safe en utilisant une AtomicReference pour le champ head.

On veut maintenant rajouter une méthode E pollFirst() qui enlève le premier élément de la liste et le renvoie. Si la liste est vide, la méthode renvoie null.

Le code non thread-safe est le suivant :

    /**
     * Removes and returns the first value of the list it's not empty. Otherwise, returns null.
     * @return the value at the start of the list if the list is not empty and null if the list is empty.
     */
    public E pollFirst() {
        if (head == null) {
            return null;
        }
        var value = head.value;
        head = head.next;
        return value;
    }    

Rajoutez la méthode E pollFirst() à votre LinkedListLockFree.