Examen 2h sur machine


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.

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

La Javadoc est là : javadoc 21.

Secret Santa

Dans cet exercice, on cherche à simuler un Secret Santa (Père Noël secret) où chacun des participants offre un cadeau à un autre participant tiré au hasard. Typiquement, tout le monde met son nom sur un morceau de papier dans un chapeau, puis quand tout le monde a mis son nom, chacun prend un morceau de papier qui sera la personne à laquelle on offrira un cadeau. On va écrire une classe thread-safe SecretSanta qui permet à plusieurs threads de faire un père Noël secret en s’inspirant de ce principe. Toutes les classes demandées doivent être dans le package secretsanta. Dans tout l'exercice, on vous demande d'utiliser l'API des ReentrantLock.

La classe SecretSanta est créé pour un nombre fixé nbParticipants de participants pris en paramètre à la construction. On fixe également à la création, l'ordre dans lequel les différents participants vont retirer les noms du chapeau (donc les participants ne prennent pas un nom au hasard). Pour cela, on donne une liste de nbParticipants entiers. Le i-ème entier de cette liste vaut j pour indiquer que le (i+1)-ème thread à avoir déposé un nom dans le chapeau doit retirer le nom qui a été mis par le (j+1)-ème thread. Ainsi, en créant un SecretSanta avec les paramètres suivants :

  var secretSanta = new SecretSanta(4, List.of(2, 3, 1, 0));

On obtient que le premier thread (celui d'indice 0) qui met un nom prendra le nom donné par le 3-ème thread. Le deuxième celui du 4-ème, le 3-ème celui du second et le 4-ème celui du premier.

Pour l'instant, la classe SecretSanta possédera les méthodes publiques suivantes :

En utilisant l'API des ReentrantLock, écrire une classe thread-safe SecretSanta comme décrite précédemment.

Pour tester votre code, vous pouvez copier le main ci-dessous.

public static void main(String[] args) {
        var secretSanta = new SecretSanta(5, List.of(0, 1, 3, 4, 2));
        int[] waitingTimes = {500, 1000, 1500, 2000, 2500};
        for (var id = 0; id < waitingTimes.length; id++) {
            var waitingTime = waitingTimes[id];
            Thread.ofPlatform().name("Thread " + id).start(() -> {
                try {
                    Thread.sleep(waitingTime);
                    var currentThreadName = Thread.currentThread().getName();
                    System.out.println(currentThreadName + " received " + secretSanta.submit(currentThreadName));
                } catch (InterruptedException e) {
                    throw new AssertionError(e);
                }
            });
        }
    }    

Vous devriez observer les lignes ci-dessous mais possiblement dans un autre ordre d'affichage.

Thread 4 received Thread 2
Thread 0 received Thread 0
Thread 3 received Thread 4
Thread 2 received Thread 3
Thread 1 received Thread 1

On veut maintenant rajouter une méthode List<String> observe(int n) qui attend que n noms aient été proposés et renvoie la liste des n premiers noms proposés. Si un thread appelant observe est interrompu, on veut que ce thread lève une InterruptedException et que tous les threads en attente dans un appel à observe lèvent une InterruptedException. Si un thread tente d'appeler observe à l'avenir, il devra lever une IllegalStateException. Attention, les threads qui appellent submit ne doivent pas être affectés.

Rajouter la méthode observe décrite ci-dessus à votre classe SecretSanta.

Pour tester votre code, vous pouvez rajouter le code suivant dans le main de votre classe Application :

int[] observedSizes = {1,2,3,4,5};
var observers = new Thread[observedSizes.length];
for (var id = 0; id < observedSizes.length; id++) {
    var observedSize = observedSizes[id];
    observers[id]=Thread.ofPlatform().name("Observer " + id).start(() -> {
        var observerName = Thread.currentThread().getName();
        try {
            System.out.println(observerName + " observed " + secretSanta.observe(observedSize));
        } catch (InterruptedException e) {
            System.out.println(observerName + " did not observe any thing as a InterruptedException was thrown");
        }
    });
}
Thread.sleep(1100);
observers[2].interrupt();    

Vous devriez voir un affichage du genre (notez que l'ordre des lignes n'est pas garanti) :

Observer 0 observed [Thread 0]
Observer 1 observed [Thread 0, Thread 1]
Observer 2 did not observe any thing as a InterruptedException was thrown
Observer 3 did not observe any thing as a InterruptedException was thrown
Observer 4 did not observe any thing as a InterruptedException was thrown
Thread 2 received Thread 3
Thread 3 received Thread 4
Thread 0 received Thread 0
Thread 4 received Thread 2
Thread 1 received Thread 1    

Les deux questions suivantes sont indépendantes les unes des autres.

On veut maintenant rendre la classe SecretSanta réutilisable.

Copier la classe SecretSanta dans une classe SecretSantaReusable et rendre la classe réutilisable. Plus précisément, la méthode submit ne lèvera pas d'exception s'il y a déjà nbParticipants noms qui ont été donnés, mais elle attendra que tous les threads ait retiré un nom pour utiliser l'objet.

Vous pouvez tester votre code avec le main suivant :

var nbParticipants = 5;
var secretSantaReusable = new SecretSantaReusable(nbParticipants, List.of(1, 2, 3, 4, 0));
for (var id = 0; id < nbParticipants * 3; id++) {
    Thread.ofPlatform().name("Thread " + id).start(() -> {
        try {
            var currentThreadName = Thread.currentThread().getName();
            System.out.println(currentThreadName + " received " + secretSantaReusable.submit(currentThreadName));
        } catch (InterruptedException e) {
            throw new AssertionError(e);
        }
    });
}

Vous devriez obtenir un affichage du type suivant :

Thread 9 received Thread 7
Thread 8 received Thread 5
Thread 11 received Thread 13
Thread 7 received Thread 8
Thread 10 received Thread 12
Thread 6 received Thread 9
Thread 0 received Thread 1
Thread 13 received Thread 14
Thread 14 received Thread 10
Thread 5 received Thread 6
Thread 3 received Thread 4
Thread 12 received Thread 11
Thread 1 received Thread 2
Thread 4 received Thread 0
Thread 2 received Thread 3    

Vérifiez que le main termine et que les threads échangent bien de manière circulaire par 5. Dans notre exemple, on a :

 
9 -> 7 -> 8 -> 5 -> 6 -> 9
11 -> 13 -> 14 -> 10 -> 12 
0 -> 1 -> 2 -> 3 -> 4 -> 0

On repart maintenant de la classe SecretSanta obtenue à la question 2. Vous copierez son code dans une classe SecretSantaInterrupt.
On veut modifier la méthode submit pour que, si un thread est interrompu pendant qu'il attend dans submit, la méthode lève une InterruptedException et tout se passe comme si ce thread n'avait jamais proposé de nom.

Après avoir recopié votre classe SecretSanta dans une classe SecretSantaInterrupt, modifier la méthode submit pour obtenir le comportement demandé.

Vous tester votre code, vous pouvez utiliser le main suivant :

public static void main(String[] args) throws InterruptedException {
  var secretSanta = new SecretSantaInterrupt(5, List.of(0, 1, 3, 4, 2));
  int[] waitingTimes = {500, 1_000, 1_500, 2_000, 2_500, 3_000};
  var threads = new Thread[waitingTimes.length];
  for (var id = 0; id < waitingTimes.length; id++) {
      var waitingTime = waitingTimes[id];
      threads[id]=Thread.ofPlatform().name("Thread " + id).start(() -> {
          var currentThreadName = Thread.currentThread().getName();
          try {
              Thread.sleep(waitingTime);
              System.out.println(currentThreadName + " received " + secretSanta.submit(currentThreadName));
          } catch (InterruptedException e) {
              System.out.println(currentThreadName + " has been stopped");
          }
      });
  }
  Thread.sleep(1_600);
  threads[2].interrupt();
}    

Vous devriez voir l'affichage suivant (l'ordre des lignes n'est pas garanti) :

Thread 2 has been stopped
Thread 4 received Thread 5
Thread 5 received Thread 3
Thread 0 received Thread 0
Thread 1 received Thread 1
Thread 3 received Thread 4    

Producteur-consommateur : Banco

Toutes les classes demandées doivent être dans le package banco.

Dans cet exercice, on utilise une API fictive qui permet de récupérer des transferts bancaires et de tester s'ils sont suspicieux.

L'API est téléchargeable ici Banco.java. Elle définit :

Le but de l'exercice est d'écrire une classe permettant de récupérer des transferts bancaires, de tester s'ils sont suspicieux, et d'afficher pour chaque banque la somme des montants des transferts pour cette banque. Pour paralléliser la tâche, nous allons utiliser plusieurs threads. Dans cet exercice, on vous demande d'utiliser le pattern producteur-consommateur comme seul moyen de synchronisation (en particulier, pas de join).

Plus précisément, on souhaite avoir :

On souhaite un affichage du type suivant :

---> WireTransfer[bankAccount=986115, bank=PICSOUBANK, amount=8314]
---> WireTransfer[bankAccount=23561, bank=DESSOUSSOUS, amount=-5077]
---> WireTransfer[bankAccount=183500, bank=MASSETHUNE, amount=8247]
PICSOUBANK has 8314 after WireTransfer[bankAccount=986115, bank=PICSOUBANK, amount=8314]
---> WireTransfer[bankAccount=204725, bank=PICSOUBANK, amount=-6448]
MASSETHUNE has 8247 after WireTransfer[bankAccount=183500, bank=MASSETHUNE, amount=8247]
---> WireTransfer[bankAccount=428714, bank=MASSETHUNE, amount=-7375]
PICSOUBANK has 1866 after WireTransfer[bankAccount=204725, bank=PICSOUBANK, amount=-6448]
DESSOUSSOUS has -5077 after WireTransfer[bankAccount=23561, bank=DESSOUSSOUS, amount=-5077]
---> WireTransfer[bankAccount=932031, bank=BLE, amount=-8795]
---> WireTransfer[bankAccount=694391, bank=BLE, amount=-9551]
---> WireTransfer[bankAccount=446291, bank=OSEILLE, amount=4957]
BLE has -8795 after WireTransfer[bankAccount=932031, bank=BLE, amount=-8795]
MASSETHUNE has 872 after WireTransfer[bankAccount=428714, bank=MASSETHUNE, amount=-7375]
---> WireTransfer[bankAccount=636732, bank=DESSOUSSOUS, amount=-315]
OSEILLE has 4957 after WireTransfer[bankAccount=446291, bank=OSEILLE, amount=4957]
---> WireTransfer[bankAccount=990103, bank=BLE, amount=5222]
---> WireTransfer[bankAccount=870554, bank=THUNE, amount=3776]
---> WireTransfer[bankAccount=620883, bank=BLE, amount=-5791]
DESSOUSSOUS has -5392 after WireTransfer[bankAccount=636732, bank=DESSOUSSOUS, amount=-315]
---> WireTransfer[bankAccount=293064, bank=OSEILLE, amount=-570]
---> WireTransfer[bankAccount=807162, bank=THUNE, amount=-7378]
BLE has -18346 after WireTransfer[bankAccount=694391, bank=BLE, amount=-9551]
Rejecting the suspicious WireTransfer[bankAccount=870554, bank=THUNE, amount=3776]
BLE has -13124 after WireTransfer[bankAccount=990103, bank=BLE, amount=5222]
....

Dans le main d'une classe BankProcessor, écrire le code décrit ci-dessus.

On veut maintenant changer le comportement du programme pour qu'une fois que 10 transferts suspicieux ont été détectés, le programme s'arrête.

Copier votre classe BankProcessor dans une classe BankProcessorInterrupt et dans le main effectuer les changements nécessaires pour avoir le comportement demandé.