Examen 2h sur machine


L'examen est composé d'une partie écrite sur feuille de 40 minutes maximum, à faire en premier et de deux exercices qui peuvent être traités dans l'ordre que vous voulez.

Rappel : si ce n'est pas déjà le cas, vous devez configurer le workspace d'Eclipse (File > Switch WorkSpace) pour qu'il corresponde au répertoire EXAM présent dans le home de votre session de TP noté.

Vérifier que tous vos fichiers sont bien dans le répertoire EXAM. Tout ce qui n'est pas dans ce répertoire est perdu quand vous vous déconnectez.

La javadoc 23 est là : https://igm.univ-mlv.fr/~juge/javadoc-23/.

Vous avez le droit de consulter les transparents du cours pendant tout l'examen.

QCM (sur papier)

Bloqueur de threads (synchronized)

Les classes de cet exercice doivent être dans package fr.uge.concurrence.exo1.

Dans cet exercice, on cherche à créer une classe thread-safe ThreadStopper qui va permettre à plusieurs threads de se synchroniser. La classe ThreadStopper est construite pour un certain nombre threshold de threads. La classe ThreadStopper possède une méthode participate(). Tant que cette méthode n'a pas été appelée par threshold threads, les threads qui l'appellent restent bloqués. Lorsque threshold threads l'ont appelée, tous les threads sont débloqués. Si plus de threshold threads appellent la méthode particpate, la méthode lève une exception IllegalStateException.

Écrire la classe thread-safe ThreadStopper et sa méthode participate(). Pour cet exercice, la gestion de la concurrence doit être faite avec des blocs synchronized.

Vous pouvez tester votre code dans une classe Main avec le code suivant.

    public static void main(String[] args) throws InterruptedException {
        var nbThreads = 10;
        var threadStopper = new ThreadStopper(nbThreads);
        LongStream.range(0, nbThreads).forEach(id -> {
            Thread.ofPlatform().start(() -> {
                try {
                    Thread.sleep(id * 1_000);
                    System.out.println("Thread " + id + " starts participating and is blocked");
                    threadStopper.participate();
                    System.out.println("Thread " + id + " has finished and is unblocked");
                } catch (InterruptedException e) {
                    throw new AssertionError();
                }
            });
        });
    }
Vous devriez avoir un affichage similaire à ceci :
Thread 0 starts participating and is blocked
Thread 1 starts participating and is blocked
Thread 2 starts participating and is blocked
Thread 3 starts participating and is blocked
Thread 4 starts participating and is blocked
Thread 5 starts participating and is blocked
Thread 6 starts participating and is blocked
Thread 7 starts participating and is blocked
Thread 8 starts participating and is blocked
Thread 9 starts participating and is blocked
Thread 9 has finished and is unblocked
Thread 3 has finished and is unblocked
Thread 5 has finished and is unblocked
Thread 0 has finished and is unblocked
Thread 6 has finished and is unblocked
Thread 7 has finished and is unblocked
Thread 8 has finished and is unblocked
Thread 4 has finished and is unblocked
Thread 2 has finished and is unblocked
Thread 1 has finished and is unblocked


On veut maintenant ajouter une méthode Set<Thread> waiting() à la classe ThreadStopper qui renvoie l'ensemble des objets Thread qui sont bloqués.

Dupliquer le code de ThreadStopper de la question 1 dans une classe ThreadStopperQ2 et rajouter la méthode waiting.

Vous pouvez tester avec le code suivant.

    public static void main(String[] args) throws InterruptedException {
        var nbThreads = 10;
        var threadStopper = new ThreadStopperQ2(nbThreads);
        LongStream.range(0, nbThreads).forEach(id -> {
            Thread.ofPlatform().start(() -> {
                try {
                    Thread.sleep(id * 1_000);
                    System.out.println("Thread " + id + " starts participating and is blocked");
                    threadStopper.participate();
                    System.out.println("Thread " + id + " has finished and is unblocked");
                } catch (InterruptedException e) {
                    throw new AssertionError();
                }
            });
        });
        Thread.sleep(2_000);
        var waiting = threadStopper.waiting();
        while (!waiting.isEmpty()) {
            Thread.sleep(500);
            System.out.println("Threads waiting : " + threadStopper.waiting().stream().map(Thread::getName).toList());
            waiting = threadStopper.waiting();
        }
    }
Vous devriez avoir un affichage similaire à ceci :
Thread 0 starts participating and is blocked
Thread 1 starts participating and is blocked
Thread 2 starts participating and is blocked
Threads waiting : [Thread-2, Thread-1, Thread-0]
Thread 3 starts participating and is blocked
Threads waiting : [Thread-2, Thread-3, Thread-0, Thread-1]
Threads waiting : [Thread-2, Thread-3, Thread-0, Thread-1]
Thread 4 starts participating and is blocked
Threads waiting : [Thread-2, Thread-0, Thread-4, Thread-3, Thread-1]
Threads waiting : [Thread-2, Thread-0, Thread-4, Thread-3, Thread-1]
Thread 5 starts participating and is blocked
Threads waiting : [Thread-4, Thread-2, Thread-5, Thread-3, Thread-0, Thread-1]
Threads waiting : [Thread-4, Thread-2, Thread-5, Thread-3, Thread-0, Thread-1]
Thread 6 starts participating and is blocked
Threads waiting : [Thread-3, Thread-2, Thread-6, Thread-5, Thread-1, Thread-4, Thread-0]
Threads waiting : [Thread-3, Thread-2, Thread-6, Thread-5, Thread-1, Thread-4, Thread-0]
Thread 7 starts participating and is blocked
Threads waiting : [Thread-7, Thread-2, Thread-3, Thread-1, Thread-5, Thread-6, Thread-0, Thread-4]
Threads waiting : [Thread-7, Thread-2, Thread-3, Thread-1, Thread-5, Thread-6, Thread-0, Thread-4]
Thread 8 starts participating and is blocked
Threads waiting : [Thread-2, Thread-1, Thread-4, Thread-0, Thread-8, Thread-5, Thread-7, Thread-3, Thread-6]
Threads waiting : [Thread-2, Thread-1, Thread-4, Thread-0, Thread-8, Thread-5, Thread-7, Thread-3, Thread-6]
Thread 9 starts participating and is blocked
Thread 9 has finished and is unblocked
Thread 5 has finished and is unblocked
Thread 2 has finished and is unblocked
Thread 8 has finished and is unblocked
Thread 0 has finished and is unblocked
Thread 7 has finished and is unblocked
Thread 6 has finished and is unblocked
Thread 3 has finished and is unblocked
Thread 4 has finished and is unblocked
Thread 1 has finished and is unblocked
Threads waiting : []


On veut maintenant ajouter une méthode void release(Thread thread) à la classe ThreadStopperQ2 qui permet de relâcher le Thread passé en paramètre s'il est bloqué dans la méthode participate().

★ Dupliquer le code de ThreadStopperQ2 de la question 2 dans une classe ThreadStopperQ3 et rajouter la méthode release. Par ailleurs, la méthode participate() doit désormais renvoyer un booléen indiquant si le thread a été débloqué par un autre appel à participate() et dans ce cas la méthode renvoie true, ou par un appel à release() et dans ce cas la méthode renvoie false.

Vous pouvez tester votre code avec le main suivant :

    public static void main(String[] args) throws InterruptedException {
        var nbThreads = 11;
        var threadStopper = new ThreadStopperQ3(nbThreads-1);
        var threads = new Thread[nbThreads];
        IntStream.range(0, nbThreads).forEach(id -> {
            threads[id] = Thread.ofPlatform().start(() -> {
                try {
                    Thread.sleep(id * 1_000);
                    System.out.println("Thread " + id + " starts participating and is blocked");
                    var finished = threadStopper.participate();
                    if (finished) {
                        System.out.println("Thread " + id + " has finished and is unblocked");
                        return;
                    }
                    System.out.println("Thread " + id + " was released");
                } catch (InterruptedException e) {
                    throw new AssertionError();
                }
            });
        });
        Thread.sleep(2_000);
        var waiting = threadStopper.waiting();
        while (!waiting.isEmpty()) {
            threadStopper.release(threads[5]);
            Thread.sleep(500);
            System.out.println("Threads waiting : " + threadStopper.waiting().stream().map(Thread::getName).toList());
            waiting = threadStopper.waiting();
        }
    }

Et vous pourriez obtenir un affichage qui ressemble à ceci :

Thread 0 starts participating and is blocked
Thread 1 starts participating and is blocked
Thread 2 starts participating and is blocked
Threads waiting : [Thread-2, Thread-1, Thread-0]
Thread 3 starts participating and is blocked
Threads waiting : [Thread-2, Thread-1, Thread-3, Thread-0]
Threads waiting : [Thread-2, Thread-1, Thread-3, Thread-0]
Thread 4 starts participating and is blocked
Threads waiting : [Thread-2, Thread-1, Thread-3, Thread-0, Thread-4]
Threads waiting : [Thread-2, Thread-1, Thread-3, Thread-0, Thread-4]
Thread 5 starts participating and is blocked
Threads waiting : [Thread-2, Thread-5, Thread-1, Thread-3, Thread-0, Thread-4]
Thread 5 was released
Threads waiting : [Thread-2, Thread-1, Thread-3, Thread-0, Thread-4]
Thread 6 starts participating and is blocked
Threads waiting : [Thread-2, Thread-1, Thread-3, Thread-0, Thread-4, Thread-6]
Threads waiting : [Thread-2, Thread-1, Thread-3, Thread-0, Thread-4, Thread-6]
Thread 7 starts participating and is blocked
Threads waiting : [Thread-2, Thread-1, Thread-7, Thread-3, Thread-0, Thread-4, Thread-6]
Threads waiting : [Thread-2, Thread-1, Thread-7, Thread-3, Thread-0, Thread-4, Thread-6]
Thread 8 starts participating and is blocked
Threads waiting : [Thread-2, Thread-1, Thread-7, Thread-8, Thread-3, Thread-0, Thread-4, Thread-6]
Threads waiting : [Thread-2, Thread-1, Thread-7, Thread-8, Thread-3, Thread-0, Thread-4, Thread-6]
Thread 9 starts participating and is blocked
Threads waiting : [Thread-2, Thread-1, Thread-7, Thread-8, Thread-3, Thread-0, Thread-4, Thread-6, Thread-9]
Threads waiting : [Thread-2, Thread-1, Thread-7, Thread-8, Thread-3, Thread-0, Thread-4, Thread-6, Thread-9]
Thread 10 starts participating and is blocked
Thread 10 has finished and is unblocked
Thread 9 has finished and is unblocked
Thread 0 has finished and is unblocked
Thread 7 has finished and is unblocked
Thread 4 has finished and is unblocked
Thread 8 has finished and is unblocked
Thread 2 has finished and is unblocked
Thread 1 has finished and is unblocked
Thread 6 has finished and is unblocked
Thread 3 has finished and is unblocked
Threads waiting : []

Tirelire (ReentrantLock)

Les classes de cet exercice doivent être dans le package fr.uge.concurrence.exo2.

On veut écrire un programme qui permet à des parents de mettre des pièces dans une tirelire et à leurs enfants d'en prendre. Les enfants s'arrêtent de prendre des pièces quand ils ont économisé une somme supérieure ou égale à TARGET_VALUE. Le tout doit se passer de façon concurrente, en utilisant une classe thread-safe PiggyBank appropriée.

La classe PiggyBank contiendra les constantes publiques suivantes :

    public class PiggyBank {
        public static final int MIN_COINS = 5;
        public static final int MAX_COIN_VALUE = 4;
        public static final int MAX_SUM = 30;
        public static final int TARGET_VALUE = 20;
    }

Il y a MAX_COIN_VALUE types de pièce de monnaie différents qui valent respectivement 1, 2, ..., MAX_COIN_VALUE. La valeur totale des pièces contenues dans la tirelire ne doit jamais dépasser MAX_SUM.

Plus précisément, 2 threads jouent le rôle de parents. Ils vont essayer, en boucle, de mettre des pièces dont la valeur est tirée au hasard dans la tirelire, tant que ça ne fait pas dépasser la valeur totale de la tirelire du maximum MAX_SUM. Si le maximum est atteint, les parents attendent que ce soit de nouveau possible avant d'ajouter une pièce. Pendant ce temps-là, 3 threads qui jouent le rôle des enfants essaient, en boucle, de prendre des pièces dans la tirelire, s'il y en a. Sinon, ils attendent jusqu'à ce que ce soit possible. Par défaut les enfants prennent une seule pièce (celle qui a été mise depuis le plus longtemps dans la tirelire). Mais si, par chance, il y a au moins MIN_COINS = 5 pièces dans la tirelire, ils en prennent exactement MIN_COINS d'un coup. Ce sont toujours les pièces qui ont été ajoutées depuis le plus longtemps qui sont retirées en premier. Lorsqu'un enfant a accumulé des pièces pour une valeur d'au moins TARGET_VALUE, son thread s'arrête. Quand tous les enfants ont assez d'argent, le programme s'arrête.

Chacun des threads affiche les actions qu'il réalise au fur et à mesure.

La valeur des pièces doit être tirée au hasard entre 1 et MAX_COIN_VALUE. Pour ça, vous pouvez écrire :

var coin = ThreadLocalRandom.current().nextInt(MAX_COIN_VALUE) + 1;

Trouvez le contrat et écrivez le code d'une classe thread-safe PiggyBank qui permettra de faire communiquer tous les threads ensemble (pour cet exercice, la synchronisation devra être faite uniquement avec des ReentrantLock).
Dans le main d'une classe Application écrivez le code réalisant le programme décrit plus haut.

On rappelle qu'il ne peut y avoir aucune synchronisation (y compris pas de join) dans le code du main et que les méthodes de la classe thread-safe ne doivent pas faire d'affichage.

Pour vous aider, voici un exemple d'affichage que l'on peut obtenir. Ici, on fait attendre chaque parent entre 0 et 1 seconde avec Thread.sleep(ThreadLocalRandom.current().nextInt(1000)) avant d'effectuer chaque action et on fait attendre chaque enfant entre 0 et 2 secondes.

parent 2 gives a coin of value 1
parent 2 gives a coin of value 2
parent 1 gives a coin of value 4
child 3 collects 1 coin for value 1
parent 2 gives a coin of value 2
child 2 collects 1 coin for value 2
parent 2 gives a coin of value 2
parent 1 gives a coin of value 3
parent 2 gives a coin of value 2
child 1 collects 5 coins for value 13
parent 1 gives a coin of value 1
parent 1 gives a coin of value 3
parent 1 gives a coin of value 2
child 3 collects 1 coin for value 1
parent 2 gives a coin of value 1
child 2 collects 1 coin for value 3
parent 1 gives a coin of value 1
child 3 collects 1 coin for value 2
child 1 collects 1 coin for value 1
child 3 collects 1 coin for value 1
parent 2 gives a coin of value 2
parent 1 gives a coin of value 1
child 3 collects 1 coin for value 2
parent 1 gives a coin of value 1
parent 2 gives a coin of value 2
parent 2 gives a coin of value 2
child 2 collects 1 coin for value 1
parent 2 gives a coin of value 4
parent 1 gives a coin of value 4
child 3 collects 5 coins for value 13
child 3 has enough with value 20 and stops
parent 1 gives a coin of value 2
child 1 collects 1 coin for value 2
parent 2 gives a coin of value 2
child 2 collects 1 coin for value 2
parent 1 gives a coin of value 1
child 1 collects 1 coin for value 1
parent 2 gives a coin of value 3
child 2 collects 1 coin for value 3
parent 2 gives a coin of value 1
parent 2 gives a coin of value 1
parent 2 gives a coin of value 2
child 2 collects 1 coin for value 1
parent 2 gives a coin of value 4
child 1 collects 1 coin for value 1
parent 2 gives a coin of value 1
parent 2 gives a coin of value 3
parent 1 gives a coin of value 1
parent 1 gives a coin of value 1
parent 2 gives a coin of value 3
child 2 collects 5 coins for value 11
child 2 has enough with value 23 and stops
parent 1 gives a coin of value 4
parent 1 gives a coin of value 2
parent 1 gives a coin of value 4
parent 1 gives a coin of value 2
parent 2 gives a coin of value 1
child 1 collects 5 coins for value 14
child 1 has enough with value 32 and stops