volatile
et Atomic*
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.
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
.
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
.