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. Pour les opérations atomiques complexes, vous devez utliser compareAndSet.
Écrivez une deuxième version de addFirst en utlisant une méthode de mise à jour qui prend une lambda en paramètre.
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 donné ci-dessous.
Rajoutez la méthode E pollFirst() à votre LinkedListLockFree.
/**
* 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;
}