Concurrence

Retours sur le TD 04


Exo 2

BoundedSafeQueue

BoundedSafeQueue

public class BoundedSafeQueue<V> {
	private final ArrayDeque<V> queue = new ArrayDeque<>();
	private final int capacity;

	...

	public void add(V value) throws InterruptedException {
		Objects.requireNonNull(value);
		synchronized (queue) {
			while (queue.size() >= capacity) { 
				queue.wait(); 
			}
			queue.add(value);
			queue.notify();
		}
	}

	public V take() throws InterruptedException {
		synchronized (queue) {
			while (queue.isEmpty()) { 
				queue.wait(); 
			}
			queue.notify();
			return queue.remove();
		}
	}	

Quel est le problème?

Quel est le problème?

Supposons que 2 threads nommés t1 et t2 font des add et qu'un 3e thread, le main, fait des take, en continu, sur une file de capacité 1.

  • Le thread t1 démarre. La file est vide, il ajoute et fait une notification perdue. Il essaie d'ajouter à nouveau. La file est pleine, il attend.
  • Le thread t2 démarre. La file est pleine, il attend.
  • Le thread main démarre. Il retire une valeur et notifie. Puis il essaie de retirer une autre valeur, mais la file est vide, donc il attend.

  • t1 reçoit la notification, ajoute sa valeur et envoie une notification...
  • t2 reçoit la notification, mais la file est pleine donc il continue à attendre... t1 essaie d'ajouter, il ne peut pas, alors il attend aussi...
  • ... et le thread main ne recevra plus jamais de notification et donc il va attendre à l'infini (ou croiser les doigts pour un spurious wake-up).

Solution

public class BoundedSafeQueue<V> {
	private final ArrayDeque<V> queue = new ArrayDeque<>();
	private final int capacity;

	...

	public void add(V value) throws InterruptedException {
		Objects.requireNonNull(value);
		synchronized (queue) {
			while (queue.size() >= capacity) { 
				queue.wait(); 
			}
			queue.add(value);
			queue.notifyAll();
		}
	}

	public V take() throws InterruptedException {
		synchronized (queue) {
			while (queue.isEmpty()) { 
				queue.wait(); 
			}
			queue.notifyAll();
			return queue.remove();
		}
	}	

En fait, un seul des deux suffit...

Exo 2 - Max Bloquant

  • Question 1 - contrat :
    • Besoin d'une seule méthode supplémentaire, qui bloque tant qu'aucune valeur >8000 n'a été proposée.
    • La méthode permettant de proposer des valeurs doit notifier quand on atteint 8000+.
  • Question 2 - code :
    • Surtout pas de boucle pour attendre dans le main.
  • Question 3 - optionnelle :
    • Faire attention au cas où un thread met à jour le maximum entre le moment où on dépasse 8000 et le moment où on l'affiche.

Exo 3 - Vote

On veut une classe thread-safe pour faire un vote. Quand n votes ont été enregistrés, on renvoie à chaque votant la String qui a reçu le plus de votes.

  • une seule méthode vote qui sert à proposer son vote et qui bloque jusqu'à ce que les n votes soient arrivés ; elle renvoie le gagnant.
  • si vote est appelée après que les n votes aient été reçus, la méthode renvoie simplement le gagnant.

Principaux problèmes constatés :

  • On calcule le gagnant après la fin du vote soit fini. Si un votant arrive après la fin du vote, il peut modifier le résultat !
  • On recalcule le gagnant à chaque fois qu'une valeur est renvoyée.
  • Trop de notifications : chaque thread qui sort d'un wait notifie tous les autres.

Gestion des exceptions

Pour l'instant, nous ne gérons pas les InteruptedException et rien dans votre code ne peut provoquer la levée de cette exception.

  • si c'est possible, on renvoie l'exception (avec un throws),
  • sinon (par exemple dans un Runnable), on attrape l'exception et on la relance à run time.
    Runnable run = () -> {
        try {
        	Thread.sleep(20_000);
        } catch (InterruptedException e) {
        	// this should not happen !
        	throw new AssertionError(e);
        }
    };
    

On ne doit jamais voir e.printStackTrace();