Signaux (suite) - Deadlock

Exchanger

On souhaite implanter un Exchanger qui permet d'échanger deux valeurs entre deux threads.

L'idée est qu'un premier thread va envoyer une valeur à l'Exchanger en utilisant la méthode exchange, celui-ci va bloquer le thread qui a fait appel à la méthode exchange et attendre (on suppose qu'il n'y a que 2 threads et que chacun appelle exchange exactement une fois). Lorsque un second thread fait lui aussi un appel à la méthode exchange avec une seconde valeur, l'appel retourne la première valeur envoyée et le premier thread est dé-bloqué de son appel à exchange en retournant la seconde valeur (attention, les valeurs peuvent être nulles).

En fait, la classe Exchanger existe déjà en Java (dans le package java.util.concurrent), et voici un exemple de code l'utilisant.

ExchangerExample.java
public class ExchangerExample {
  public static void main(String[] args) throws InterruptedException {
    var exchanger = new Exchanger<String>();
    Thread.ofPlatform().start(() -> {
      try {
        System.out.println("thread 1 " + exchanger.exchange("foo1"));
      } catch (InterruptedException e) {
        throw new AssertionError(e);
      }
    });
    System.out.println("main " + exchanger.exchange(null));
  }
}

Quel est l'affichage attendu ?

On se propose de ré-implanter cette classe.

Comment faire pour distinguer le premier et le second appel à la méthode exchange ?

Écrire le code de la classe Exchanger.

Exchanger (suite)

L'Exchanger est-il utilisable plus d'une fois ?

Autrement dit, on veut pouvoir exécuter un code comme celui-ci: ExampleExchanger2.java

public class Example {
  public static void main(String[] args) throws InterruptedException {
    var exchanger = new ExchangerReuse<String>();
    IntStream.range(0, 10).forEach(i -> {
      Thread.ofPlatform().start(() -> {
        try {
          System.out.println("thread " + i + " received from " + exchanger.exchange("thread " + i));
        } catch (InterruptedException e) {
          throw new AssertionError(e);
        }
      });
    });
  }
}  

et obtenir un affichage du type :

thread 2 received from thread 0
thread 0 received from thread 2
thread 3 received from thread 8
thread 8 received from thread 3
thread 5 received from thread 9
thread 9 received from thread 5
thread 1 received from thread 7
thread 7 received from thread 1
thread 4 received from thread 6
thread 6 received from thread 4

Dupliquer la classe Exchanger dans une classe ReusableExchanger et commencer par modifier votre méthode d'échange pour encoder l'état de l'Exchanger avec un enum.

Proposer une version réutilisable ReusableExchanger de l'Exchanger.

Indication : utiliser trois états permettant de savoir si l'échange n'est pas commencé, en cours ou fini.

Contrat de classe thread-safe

L'exercice suivant est à nouveau un exercice de contrat : on vous demande d'écrire une classe thread-safe sans spécifier quelles sont les méthodes de la classe. Pour un problème donné, c'est à vous de décider quelle classe vous allez créer, afin qu'elle soit utilisée par plusieurs threads, pour résoudre ce problème. Une telle classe doit être une librairie, relativement simple, avec un rôle bien identifié. En particulier, les méthodes de cette classe ne sont pas sensées faire des calculs très longs ni des affichages.

Dans cet exercice, on utilise une API pour qui permet d'obtenir la température d'une pièce de la maison (on imagine que chaque pièce de la maison a un capteur relié à internet). L'API permet d'obtenir la température d'une pièce à partir de son nom (une String). Cette requête prend un peu de temps. L'API se résume à la classe Heat4J qui ne possède qu'une méthode statique retrieveTemperature(String roomName). Elle s'utilise tout simplement comme suit :

   var temperature = Heat4J.retrieveTemperature("kitchen");

Pour des raisons de simplicité l'API, ici le code fournit ne fait pas vraiment de requête. Il attend un peu et tire simplement la température au hasard.

Le main de la classe Application ci-dessous demande la température de chaque pièce de la maison et calcule la moyenne des températures et l'affiche.

import java.util.ArrayList;
import java.util.List;

import com.domo.Heat4J;

public class Application {
  public static void main(String[] args) throws InterruptedException {
    var rooms = List.of("bedroom1", "bedroom2", "kitchen", "dining-room", "bathroom", "toilets");

    var temperatures = new ArrayList<Integer>();

    for (String room : rooms) {
      var temperature = Heat4J.retrieveTemperature(room);
      System.out.println("Temperature in room " + room + " : " + temperature);
      temperatures.add(temperature);
    }

    System.out.println(temperatures.stream().mapToInt(Integer::intValue).average().getAsDouble());
  }
}

Quand on l’exécute, on obtient un affichage qui ressemble à ceci (les valeurs peuvent changer car elles sont tirées au hasard) :

Temperature in room bedroom1 : 19
Temperature in room bedroom2 : 20
Temperature in room kitchen : 26
Temperature in room dining-room : 25
Temperature in room bathroom : 24
Temperature in room toilets : 28
23.666666666666668

Copier les classes Heat4J.java et Application.java et vérifier que vous pouvez exécuter le main de Application.

Le but de l'exercice est de pouvoir modifier le main de Application pour que :

Il va donc falloir faire communiquer les threads qui font les appels à Heat4J.retrieveTemperature avec le thread main. Pour cela, vous devez réaliser une classe thread-safe de votre choix qui servira à faire communiquer tous les threads. Il est interdit d'utiliser de la synchronisation en dehors de cette classe. Les méthodes de cette classe ne doivent pas faire d'affichage. De plus, pour cet exercice, on vous demande de ne pas utiliser de join() dans le main (ça fonctionne pour la première question, mais pas pour la deuxième).

Écrivez votre classe thread-safe et modifiez le main de la classe Application pour qu'il utilise cette classe et les thread indiqués ci-dessus.

(OPTIONNEL) On veut maintenant modifier comportement du main de la classe Application. On veut que chaque thread demande la température de sa pièce en permanence. De plus, si un thread redonne la température de sa pièce avant que l'ancienne valeur ait été utlisée, il est bloqué. Le main affiche la moyenne à chaque fois que tous les threads ont mis à jour la température de leur pièce.
Créez une nouvelle classe thread-safe et une classe ApplicationBis qui réalise la tâche demandée.

Le déjeuner des philosophes (facultatif, mais important pour la culture générale)

Cinq philosophes (initialement, mais il peut y en avoir beaucoup plus) se trouvent autour d'une table ronde; chaque philosophe a, devant lui, un plat de spaghetti et entre chaque assiette se trouve une fourchette. Pour pouvoir manger, un philosophe doit prendre deux fourchettes, celle à sa droite et celle à sa gauche.

Un de vos collègues propose le code suivant pour modéliser cette situation: PhilosopherDinner.java

Quel est le problème du code ci-dessus ? Dans quelle(s) condition(s) se produit-il ?
Note: vous avez le droit de l'exécuter si vous ne voyez pas.

Est-il possible d'avoir deux philosophes qui mangent en même temps ? Est-ce quelque chose qui est normal ou pas ?

Modifier le code pour corriger le problème.
On veut que plusieurs philosophes puissent manger en même temps.