On considère le code suivant. Répondez dans un fichier texte nommé exo1.txt.
public class MyClass { private String first; private String second; private final Object lock = new Object(); public MyClass(String first, String second) { this.first = first; this.second = second; } public void set(String value1, String value2) { synchronized (lock) { first = value1; second = value2; } } public void setCheckFirst(String value1, String value2) { if (value1 != null) { synchronized (lock) { first = value1; } } synchronized (lock) { second = value2; } } @Override public String toString() { synchronized (lock) { return first + " + " + second; } } public static void main(String[] args) throws InterruptedException { var quizz = new MyClass("mouse", "duck"); var thread1 = Thread.ofPlatform().daemon().start(() -> { for (;;) { quizz.set("cat", "dog"); System.out.println("1. " + quizz); } }); var thread2 = Thread.ofPlatform().start(() -> { quizz.setCheckFirst("bird", "fish"); System.out.println("2. " + quizz); }); thread2.join(); //thread1.join(); quizz.set("mouse", "duck"); System.out.println("3. " + quizz); } }
Pour chaque question, indiquez si vous pensez que l’affirmation est correcte ou si vous pensez qu’elle est fausse. Lors de l’exécution de MyClass
, ...
main
peut être dé-schédulé entre les deux affectations dans l'appel au constructeur (line 37).
thread1
peut être dé-schédulé entre les deux affectations dans son appel à la méthode set()
(line 41).
thread1
ne redonne pas le jeton du lock
entre les deux affectations dans l'appel à la méthode set()
(line 41).
set()
peut être inversé (lignes 13 et 14).
main
peut être inversé (lignes 54 et 55).
"1. mouse + duck"
.
"1. bird + fish"
.
"1. cat + fish"
.
"1. bird + dog"
.
"2. mouse + duck"
.
"2. bird + duck"
.
"3. ..."
peut être autre chose que "3. mouse + duck"
.
thread1.join();
le programme ne s'arrête pas.
Une école possède un tableau d'honneur (où l'on met le nom et le prénom de l'élève le plus méritant) qui peut être mis à jour de façon informatique. Il n'en faut pas plus pour que Mickey Mouse et Donald Duck, nos deux apprentis hackers, écrivent un petit programme qui met à jour automatiquement le tableau d'honneur avec leurs noms : HonorBoard.java.
Malheureusement, la classe HonorBoard
n'est pas thread-safe, donc le code ne se comporte pas comme prévu.
Expliquer pourquoi la classe HonorBoard
n'est pas thread-safe.
Si vous ne voyez pas, faites un grep "Mickey Duck"
sur la sortie du programme et donner un scénario pouvant mener à cet affichage.
Modifier le code de la classe HonorBoard
pour la rendre thread-safe
.
Vérifier avec grep
sur la sortie comme précédemment
(pendant plusieurs minutes).
Maintenant que votre classe est thread-safe, peut-on remplacer la ligne :
System.out.println(board);par la ligne :
System.out.println(board.firstName() + ' ' + board.lastName());avec les deux accesseurs définis comme d'habitude et en utilisant des bloc
synchronized
?
public String firstName() { synchronized (lock) { return firstName; } } public String lastName() { synchronized (lock) { return lastName; } }Vérifier en exécutant le code. La classe
HonorBoard
est-elle toujours thread-safe ?
On reprend une dernière fois le code qui entrelace les accès à une ArrayList
et cette fois-ci on ne fixe pas la capacité initiale de la
liste :
public class HelloListBug { public static void main(String[] args) throws InterruptedException { var nbThreads = 4; var threads = new Thread[nbThreads]; var list = new ArrayList<Integer>(); IntStream.range(0, nbThreads).forEach(j -> { Runnable runnable = () -> { for (var i = 0; i < 5000; i++) { list.add(i); } }; threads[j] = Thread.ofPlatform().start(runnable); }); for (Thread thread : threads) { thread.join(); } System.out.println("taille de la liste:" + list.size()); } }
Exécuter le programme plusieurs fois. Quel est le nouveau comportement observé ? Expliquer quel est le problème. Là encore, il faut regarder le code de la méthode ArrayList.add
.
Dans cet exercice, on souhaite écrire une classe RendezVous
thread-safe qui permet de réaliser le passage d'une valeur entre des threads. C'est une première tentative qui ne fonctionnera pas de réaliser une méthode bloquante. Nous verrons au prochain cours comment résoudre ce problème de façon satisfaisante.
Dans le programme FindPrime.java, plusieurs threads sont démarrés et tirent des grands nombres au hasard
jusqu'à en trouver un qui soit premier. Le principe est que le main
attend jusqu'à ce que l'un des threads ait trouvé un nombre premier. Pour réaliser le passage de la valeur entre les threads qui cherchent des nombres premiers et le main
, nous allons écrire une classe RendezVous
.
public static void main(String[] args) { var nbThreads = 4; var rdv = new StupidRendezVous(); IntStream.range(0, nbThreads).forEach(i -> { Thread.ofPlatform().daemon().start(() -> { for (;;) { var nb = BIG_LONG + ThreadLocalRandom.current().nextLong(BIG_LONG); if (isPrime(nb)) { rdv.set(nb); System.out.println("A prime number was found in thread " + i); return; } } }); }); try { System.out.println("I found a large prime number : " + rdv.get()); } catch (InterruptedException e) { throw new AssertionError(); } }
La classe RendezVous
est sensée être une classe thread-safe qui offre un méthode set
permettant de proposer une valeur et une méthode get
qui ''bloque'' jusqu'à ce qu'une valeur ait été proposée et la renvoie lorsque c'est le cas.
On commence avec une très mauvaise tentative dans la classe StupidRendezVous.java :
/** * Note: this code does several stupid things ! */ package fr.uge.concurrence; import java.util.Objects; public class StupidRendezVous<V> { private V value; public void set(V value) { Objects.requireNonNull(value); this.value = value; } public V get() throws InterruptedException { while (value == null) { Thread.sleep(1); // then comment this line ! } return value; } }
Que se passe-t-il lorsqu'on exécute ce code ?
Commenter l'instruction Thread.sleep(1)
dans la méthode get
puis ré-exécuter le code. Que se passe-t-il ?
Expliquer où est le bug ?
Écrire une classe thread-safe RendezVous
sur le même principe que
la classe StupidRendezVous
mais qui fonctionne correctement, que l'instruction Thread.sleep(1)
soit commentée ou non.
Regarder l'utilisation du CPU par votre programme avec la commande top
. Votre code fait de l'attente active ce qui n'est pas une solution acceptable, mais vous n'avez pas les outils pour corriger cela pour l'instant. Nous verrons au prochain cours comment réaliser une méthode bloquante sans faire de l'attente active.
Dans cet exercice, on va écrire un programme MaximumRace.java
qui démarre 4 threads qui tirent au hasard et affichent 10 valeurs entières, en attendant une seconde entre chaque tirage. On voudra que pendant ce temps là, le main
puisse afficher à intervalles de temps réguliers quelle est le maximum courant des valeurs proposées par l'ensemble des threads.
Voici un extrait du programme :
private static void checkedSleep(int millis) { try { Thread.sleep(millis); } catch (InterruptedException e) { throw new AssertionError(); } } public static void main(String[] args) { var nbThreads = 4; var threads = new Thread[nbThreads]; for (var j = 0; j < nbThreads; j++) { threads[j] = Thread.ofPlatform().start(() -> { for (var i = 0; i < 10; i++) { checkedSleep(1000); var value = ThreadLocalRandom.current().nextInt(SharedInformation.MAX_VALUE); System.out.println(Thread.currentThread() + " a tiré " + value); } }); } for (int i = 0; i < 10; i++) { checkedSleep(1000); System.out.println("Max courant : "); } ... }
Quatre threads sont démarrés. Chacun attend une seconde avant de tirer une valeur aléatoire dans l'intervalle [0, MAX_VALUE[
:
À ce moment là, le thread affiche la valeur et il fait ça 10 fois. MAX_VALUE = 10_000
est une constante de la classe SharedInformation
que nous allons écrire dans la suite.
Pendant ce temps, toutes les secondes, le main
affiche quel est le maximum courant parmi toutes les valeurs tirées par les threads. Après 10 affichages du maximum courant, le main attend que les autres threads aient finit et affiche le maximum final avant de terminer.
Pourquoi a-t-on besoin d'une classe thread-safe pour réaliser ce programme ? Quelles doivent être les méthodes fournies par cette classe (on dit que c'est le "contrat" de la classe) ?
Écrire la classe thread-safe SharedInformation
correspondante avec les en-têtes des ses méthodes (mais pas le code, on le fera ensuite).
Complétez le code du main
, pour qu'il réalise le travail demandé, en utilisant SharedInformation
.
Écrire le code des méthodes de SharedInformation
de façon à ce qu'elle soit thread-safe et afin que le code du
Thread[#20,Thread-0,5,main] a tiré 3431 Thread[#23,Thread-3,5,main] a tiré 1889 Thread[#22,Thread-2,5,main] a tiré 3 Thread[#21,Thread-1,5,main] a tiré 5818 Max courant : 5818 Thread[#22,Thread-2,5,main] a tiré 2621 Thread[#21,Thread-1,5,main] a tiré 4143 Thread[#20,Thread-0,5,main] a tiré 4022 Thread[#23,Thread-3,5,main] a tiré 666 Max courant : 5818 Thread[#20,Thread-0,5,main] a tiré 2587 Thread[#22,Thread-2,5,main] a tiré 3342 Thread[#21,Thread-1,5,main] a tiré 9445 Thread[#23,Thread-3,5,main] a tiré 5829 Max courant : 9445 ... Max final : 9445
On souhaite maintenant pouvoir afficher le thread qui a proposé le maximum en même temps que sa valeur. Modifier le code en conséquence.