:: Enseignements :: ESIPE :: E4INFO :: 2013-2014 :: Java Réseau I - Concurrence et E/S ::
[LOGO]

Examen de concurrence sur table


Exercice 1 - Questions de cours (7)

Répondez aux questions suivantes en deux ou trois phrases, pas plus.

  1. Expliquer ce que veut dire qu'une classe est thread-safe.
  2. Indiquer ce qu'est le problème de publication.
  3. Expliquer pourquoi faire un synchronized sur this n'est pas une bonne idée. Que risque t'on ?
  4. Expliquer pourquoi on a pas de problème de publication dans le code suivant.
            class Foo {
              private static Object object;
              static {
                object = new Object();
              }
            }
          
    Note: cela ne veut pas dire que c'est bien de faire ça en Java, juste que l'on a pas de problème de concurrence.
  5. Pourquoi lorsque l'on utilise une LinkedBlockingQueue en tant que file d'un producteur/consommateur, il faut utiliser le constructeur qui spécifie une taille maximum ?
  6. Décrire ce qui se passe lorsque l'on fait un appel à une des méthodes submit sur un ExecutorService.
  7. La classe ci-dessous a t'elle un problème de concurrence ?
    Si oui pourquoi ? Si non pourquoi ?
          class Foo {   
            private final ReentrantLock lock = new ReentrantLock();
                      
            public boolean checkDB(String tableName) {
              if (lock.tryLock()) {
                DataBase.checkTableName(tableName);
                lock.unlock();
                return true;
              }
              return false;
            }
          }
          

Exercice 2 - Nombre d'occurences de mots (4)

On cherche à calculer le nombre d'occurences de chaque mot à l'intérieur d'un fichier texte. Votre binôme ayant déjà commencé le travail, il est arrivé au code suivant qui calcule le nombre d'occurences de mots dans une ligne d'un fichier. Malheureusement, il ne se souvient plus comment ouvrir un fichier texte (et le refermer proprement) ni comment lire ligne à ligne ce fichier.

  public static Map<String, Integer> frequencyMap(Path path) throws IOException {
    HashMap<String, Integer> map = new HashMap<>();
    ...
      for(String token: line.split(" ")) {
        map.put(token, 1 + map.getOrDefault(token, 0));
      }
    ...
    return map;
  }
  
  1. Pourquoi l'interface java.nio.file.Path est une interface ?
    Quelle abstraction propose t'elle ?
    L'ancienne version de Path est java.io.File, pourquoi a t'il été nécessaire de créer une nouvelle version ?
  2. Ecrire le code correct qui permet de calculer le nombre d'occurences de chaque mot dans le fichier représenté par le Path pris en paramètre.
    Note: Vous pouvez vous passer des imports.

Exercice 3 - ProgressionManager (9)

Dans un browser web, il n'est pas rare d'effectuer plusieurs téléchargements en parallèle sur plusieurs threads, chaque thread effectuant un téléchargement. Au niveau de la partie graphique du browser, en plus de voir où en est chaque téléchargement, il est souvent possible d'avoir une vue d'ensemble indiquant sous la forme d'une seule valeur entre 0 et 100 où en est l'ensemble des téléchargements.
On se propose d'écrire une classe Java ProgressionManager dont le but est de fournir une valeur agrégée d'un ensemble de valeurs de progression indiquées par différentes threads.
Pour simplifier le problème nous allons considérer que le nombre de threads dont on veut surveiller la progression est connu à la création d'une instance de ProgressionManager et ne change pas pour toute la durée de vie de l'objet.
Enfin pour vous aider, il n'est pas nécessaire d'utiliser des classes du paquetage java.util.concurrent; essayez de penser à des choses simples.
public class ProgressionManager {
  //TODO
    
  public ProgressionManager(int parties) {
    if (parties < 0) {
      throw new IllegalArgumentException();
    }
    this.parties = parties;
  }

  public int globalProgress() {
    //TODO
  }

  public void progress(int completion) {
    if (completion < 0 || completion > 100) {
      throw new IllegalArgumentException();
    }
    //TODO
  }
  
  //TODO
}
    

Le constructeur prend un nombre de threads participantes en paramètre qui correspond au nombre de threads qui vont pouvoir reporter leur progression.
La méthode progress est appelée par chaque thread qui veut signaler sa progression. La valeur de progression, completion, est une valeur entre 0 et 100 (compris).
La méthode globalProgress renvoie la progression globale. On peut voir la progression globale comme étant la somme des progressions de chaque thread divisée par le nombre de threads participantes.

  1. A quoi sert la classe ThreadLocal ? On souhaite que l'exécution de globalProgress soit en O(1), c-a-d que le temps d'exécution du code de la méthode ne dépende pas du nombre de threads participantes.
    Ecrire une version de ProgressionManager qui utilise des moniteurs (synchronized) pour garantir la cohérence des données.
    Note: si vous n'arrivez pas a utiliser ThreadLocal, essayez sans !
  2. On souhaite ajouter une nouvelle méthode blockUntilDone qui, comme son nom l'indique, bloque la thread qui l'appelle (plusieurs threads peuvent appeler cette méthode, dans ce cas toutes les threads appelant la méthode blockUntilDone sont bloquées) tant que l'ensemble de toutes les threads qui reportent une progression n'ont pas leur progression à 100.
    Il serait possible d'implanter blockUntilDone en testant dans une boucle si toutes les threads sont bien complétées. Expliquer pourquoi ce n'est pas une bonne idée de procéder de la sorte. Indiquer le code de blockUntilDone et le nouveau code de progress.
  3. Ecrire une méthode main d'exemple qui crée un ProgressionManager avec 5 threads, chaque thread doit faire une boucle de 0 à 10 000 (compris) et indiquer tous les 100 tours de boucle sa progression au ProgressionManager (note: 100 * 100 == 10_000). Enfin, la thread main sera mise en attente tant que chaque thread n'a pas indiquée que sa progression est à 100.
  4. En fait, utiliser un verrou pour implanter le calcul de la progression globale marche mais n'est pas très performant. Utiliser des opérations atomiques est plus efficace.
    Indiquer le code de la classe ProgressManager qui utilise des opérations atomiques pour le calcul de la progression globale.
  5. On peut améliorer le code pour détecter une mauvaise utilisation de la classe ProgressionManager.
    1. Indiquer comment détecter que pour une thread les appels consécutif à progress sont toujours avec des valeurs de completion de plus en plus grande.
      Indiquer le code que l'on doit ajouter pour détecter cette mauvaise utilisation.
    2. Il est aussi possible qu'un utilisateur crée plus de threads qui reportent une progression que le nombre de threads indiqués à la construction de ProgressionManager.
      Comment détecter ce cas ?
      Indiquer le code que l'on doit ajouter pour détecter cette mauvaise utilisation.
    3. Enfin, il est possible qu'une thread ayant transmis une progression appelle blockUntilDone alors qu'elle n'a pas transmis 100.
      Que ce passe-t'il dans ce cas là ?
      Comment corriger le problème ?
      Indiquer le code que l'on doit ajouter pour détecter cette mauvaise utilisation.