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

Examen - Réseau - Concurrence & I/O (Septembre)


Exercice 1 - Question de cours

Chaque explication devra faire entre trois et sept lignes (Eviter le bavardage, SVP) et surtout répondez à la question.

  1. A quoi servent les classes du package java.util.concurrent.atomic. Quelles sont les avantages/inconvénients par rapport à l'utilisation de moniteur avec le bloc synchronized.
  2. Le code suivant a t'il un problème de publication ?
         public class Entry {
           private volatile int value;
           private long key;
           
           public Entry(int value, long key) {
             this.value = value;
             this.key = key;
           }
         }
        
    Si oui, pourquoi ? si non pourquoi ?
  3. Pourquoi un notify sur un objet doit être appelé dans un bloc synchronized sur le même objet. Quelle serait le ou les problèmes si Java n'imposait pas cette restriction.
  4. Quelles sont les deux raisons pour lesquels il faut mettre les appels à wait à l'intérieur d'une boucle while.
  5. En s'appuyant sur votre propre expérience, dans quel(les) cas utilise t'on un java.io.Reader plutôt qu'un java.io.InputStream ? NB: je ne veux pas ce qu'il y a marqué dans le cours mais que vous répondiez à la question.
  6. A quoi sert la méthode tryLock de l'interface java.util.concurrent.lock.Lock ?

Exercice 2 - Calcul en parallèle

On cherche à effectuer la somme des élements d'un tableau de double en parallèle. Pour cela, un étudiant n'ayant pas appris son cours produit le code suivant:

  static double sum(double[] array, int start, int end) {
    double sum = 0.0;
    for(int i = start; i < end; i++) {
      sum += array[i];
    }
    return sum;
  }
  
  public static double concurrentSum(int numberOfThread, final double[] array) {
    int pos = 0;
    int length = array.length/ numberOfThread; 
    final double[] result = new double[numberOfThread];
    Thread[] threads = new Thread[numberOfThread];
    
    for(int i=0; i < numberOfThread; i++) {
      final int threadIndex = i;
      final int start = pos;
      final int end = (i == numberOfThread - 1)? array.length: pos + length;
      
      Thread thread = new Thread(new Runnable() {
        @Override
        public void run() {
          result[threadIndex] = sum(array, start, end);
        } 
      });
      threads[threadIndex] = thread; 
      thread.start();
      
      pos = end;
    }
    
    return sum(result, 0, result.length);
  }
  
  1. Rappeler ce qu'est une race-condition.
    Pourquoi il y a une race-condition dans le code ci-dessus.
  2. Modifier le code pour que celui-ci soit juste sachant que l'on vous demande de faire le moins de modifications possibles. On ne vous demande pas d'utiliser un executor ici (cf question suivante)
  3. Indiquer ce que l'on doit changer dans le code ci-dessus pour utiliser un executor plutôt que de créer les threads à la main. Le code devra aussi être libre de race-condition.

Exercice 3 - Buffer bloquant

Le code suivant fourni d'une Queue FIFO sous forme de liste chainée. L'opération put permet d'ajouter un élement à la fin de la liste, l'opération take retire un élement à la tête de la liste.
On souhaite modifier cette implamntation pour pouvoir l'utiliser en tant que buffer d'un schéma producteur/consommateur.

  public class BlockingLinkedBuffer<E> {
    static class Link<E> {
      final E element;
      Link<E> next;
      
      public Link(E element) {
        this.element = element;
      }
    }
    
    private Link<E> head;
    private Link<E> tail;
    
    public void put(E element) {
      Link<E> link = new Link<E>(element);
      if (head == null) {
        head = link;
      } else {
        tail.next = link;
      }
      tail = link;
    }
    
    public E take() {
      if (head == null)
        throw new IllegalStateException("buffer is empty");
      
      Link<E> link = head;
      if (link.next == null) {
        tail = null; 
      }
      head = link.next;
      return link.element;
    }
  }
  
  1. Rappeler qu'elle est le nom de l'interface définissant les opérations de base sur les buffers producteur/consommateur en Java.
    Note: par la suite nous ne l'utiliseront pas, c'est juste une question de cours.
  2. Rappeler pourquoi dans un schéma producteur/consommateur le buffer doit bloquer la thread appelante si il n'y a pas d'élement dans le buffer plutôt que de renvoyer une exception comme dans le code ci-dessus.
  3. Pourquoi doit-on aussi bloquer une thread si il y a plus d'élements dans le buffer qu'un nombre fixé à l'avance ?
  4. Indiquer le code d'un buffer producteur/consommateur en ré-utilisant la partie algorithmique de la classe ci-dessus, bloquant les threads des producteurs/consommateur quand il faut sachant que le nombre maximal d'élément sera 500. (vous pouvez mettre le chiffre en dure, ce n'est pas cela qui m'intéresse). Vous devrez utiliser les verrous (Lock) pour répondre à ce problème (cf question suivante).
  5. Au lieu de bloquer une thread essayant de retirer un élement dans une Queue vide; Il est préférable si l machine faisant tourner le prgramme possède plusieurs processeurs de ne pas bloquer la thread tout de suite mais de tester activement la condition. On appel ce genre de code spin-lock, au lieu de mettre le verrou en attente, on va tester la condition dans un boucle car comme il y a plusieurs processeurs, une thread tournant sur un autre processeur peu ajouter des élements entre deux tests.
    Indiquer un code permettant de faire un spin-lock dans le cas ou la Queue est vide sachant que l'on fera le test au maximum 100 foix (pareil la constante peut être en dure).