$Id: index.texi,v 1.22 2010/09/29 13:18:22 pautet Exp $
Laurent Pautet (pautet@telecom-paristech.fr)
Ce TP ne présente pas tous les outils proposés par Java pour gérer la concurrence. Il illustre le fonctionnement de quelques uns d’entre eux, en commençant par ceux de plus bas niveau, wait() et notify() documentés ici. Le support de cours sur les threads se trouve ici.
Pour accéder à :
Vous trouverez dans cette archive compressée l’intégralité des sources.
Pour décompresser, utiliser GNU tar:
/usr/local/bin/tar zxf src.tar.gz
Les sémaphores tels que définis par Dijkstra sont maintenant disponibles dans le JDK-1.5. Nous ne les utiliserons pas et au contraire, cet exercice vise à utiliser les méthodes de bas niveau, comme wait() et notify(), pour créer ces outils de plus haut niveau que sont les sémaphores. Nous nous proposons de réaliser en Java les opérations classiques P (ou acquire) et V (ou release) des sémaphores.
Pour l’opération P (ou acquire):
Pour l’opération V (ou release):
Nous partirons des canevas proposés dans les fichiers Semaphore.java, Agent.java et SemaphoreMain.java.
Questions
Après avoir complété ces fichiers, nous vérifierons que l’exécution de la commande :
javac SemaphoreMain.java java SemaphoreMain 3 1
Donne une séquence d’exécution de la forme suivante :
main terminated Agent 1 WANTS to acquire resource Agent 1 ACQUIRE resource Agent 1 RELEASE resource Agent 0 WANTS to acquire resource Agent 0 ACQUIRE resource Agent 0 RELEASE resource Agent 2 WANTS to acquire resource Agent 2 ACQUIRE resource Agent 2 RELEASE resource Agent 0 WANTS to acquire resource Agent 0 ACQUIRE resource Agent 0 RELEASE resource Agent 2 WANTS to acquire resource Agent 2 ACQUIRE resource Agent 1 WANTS to acquire resource Agent 2 RELEASE resource Agent 1 ACQUIRE resource Agent 1 RELEASE resource Agent 0 WANTS to acquire resource Agent 0 ACQUIRE resource Agent 0 RELEASE resource Agent 0 completed Agent 2 WANTS to acquire resource Agent 2 ACQUIRE resource Agent 2 RELEASE resource Agent 2 completed Agent 1 WANTS to acquire resource Agent 1 ACQUIRE resource Agent 1 RELEASE resource Agent 1 completed
Puis vérifier également le bon fonctionnement des scénarii suivants.
java SemaphoreMain 0 1
main terminated
java SemaphoreMain 0 0
main terminated
java SemaphoreMain 2 3
main terminated Agent 1 WANTS to acquire resource Agent 1 ACQUIRE resource Agent 1 RELEASE resource Agent 0 WANTS to acquire resource Agent 0 ACQUIRE resource Agent 0 RELEASE resource Agent 0 WANTS to acquire resource Agent 0 ACQUIRE resource Agent 0 RELEASE resource Agent 0 WANTS to acquire resource Agent 0 ACQUIRE resource Agent 1 WANTS to acquire resource Agent 1 ACQUIRE resource Agent 0 RELEASE resource Agent 0 completed Agent 1 RELEASE resource Agent 1 WANTS to acquire resource Agent 1 ACQUIRE resource Agent 1 RELEASE resource Agent 1 completed
En reprenant le sémaphore réalisé dans l’exemple précédent, nous allons illuster le schéma producteur/consommateur dans le cas où P producteurs et C consommateurs travaillent en utilisant un tampon de dimension N. Dès lors, un producteur ne produit que si le tampon n’est pas plein et le consommateur ne consomme que si le tampon n’est pas vide.
Nous rappellons le schéma producteur/consommateur géré par deux sémaphores emptySlots et fullSlots, le tampon contenant N cases. Il faut également une exclusion mutuelle pour assurer que plusieurs consommateurs ou plusieurs producteurs ne modifient pas le tampon en même temps. Cette exclusion mutuelle sera mise en oeuvre directement à partir de Java.
Init (EmptySlots, N);
Init (FullSlots, 0);
Producteur Consommateur ... ... |
Nous partirons des squelettes proposés dans les fichiers BoundedBuffer.java, SemConsumer.java, SemProducer.java, SemBoundedBuffer.java et SemBoundedBufferMain.java.
Questions
javac SemBoundedBufferMain.java java SemBoundedBufferMain 4 4 2
donne une séquence d’exécution de la forme suivante :
java BoundedBufferMain 4 4 2 Producer 0 want to put value Producer 0 put value 1 main completed Producer 1 want to put value Producer 1 put value 1 Producer 2 want to put value Consumer 0 want to get value Consumer 0 got value 1 Producer 2 put value 4 Consumer 1 want to get value Consumer 1 got value 1 Producer 0 want to put value Producer 0 put value 9 Producer 2 want to put value Consumer 3 want to get value Consumer 3 got value 4 Producer 2 put value 2 Consumer 0 want to get value Consumer 0 got value 9 Producer 1 want to put value Producer 1 put value 4 ...
Nous allons écrire de nouveau le scénario producteur/consommateur impliquant P producteurs et C consommateurs partageant un tampon de taille fixe à N cases. Cependant, nous ne souhaitons pas cette fois réaliser l’implantation à l’aide de sémaphores mais directement à l’aide des méthodes wait(), notify() et notifyAll().
Par ailleurs, nous souhaitons que les méthodes get() et put() disposent d’une version qui permette de ne pas attendre au delà d’un certain nombre de milli-secondes, un paramètre supplémentaire timeout indiquant le temps d’attente.
Nous partirons des squelettes proposés dans les fichiers TimedConsumer.java, TimedProducer.java, TimedBoundedBuffer.java et TimedBoundedBufferMain.java.
Questions
java TimedBoundedBufferMain 2 4 2 main completed Producer 0 want to put value Producer 0 put value 4 Producer 2 want to put value Producer 2 put value 0 Producer 3 want to put value Producer 2 want to put value Producer 0 want to put value Consumer 0 want to get value Consumer 0 got value 4 Producer 3 put value 5 Producer 1 want to put value Consumer 1 want to get value Consumer 1 got value 0 Producer 2 put value 8 Consumer 0 want to get value Consumer 0 got value 5 Producer 0 put value 6 Producer 3 want to put value Producer 2 want to put value Consumer 1 want to get value Consumer 1 got value 8 Producer 1 INTERRUPTED Producer 3 INTERRUPTED Producer 2 put value 7 Consumer 0 want to get value Consumer 0 got value 6 Producer 0 want to put value Producer 0 put value 8 ...
Nous allons écrire une application qui gère un groupe de N travaux en les faisant traiter par un groupe de M travailleurs (threads). L’objectif est de limiter le nombre de threads (donc M < N).
Nous utiliserons ici wait(), notify() et notifyAll() documentés ici.
Scénario :
Comme indiqué, il s’agit d’affecter des travaux à un nombre restreint de threads. Deux types de threads sont utilisés : Master et Worker.
Nous allons utiliser des barrières pour assurer les points de synchronisation entre Master et Worker.
Définition et utilisation d’une barrière :
Une barrière est un outil de synchronisation qui fournit les fonctionnalités suivantes :
Un cas classique d’utilisation est de considérer comme ressource la présence d’un thread à un point de rendez-vous. Pour s’assurer que N threads sont présents à un même point de synchronisation, nous définissons une barrière initialisée à N. Chaque thread arrivant au point de synchronisation enchaînera les appels à Countdown pour signaler sa présence, puis à Await pour attendre l’arrivée des autres threads. Naturellement, si nous voulons réutiliser cette barrière, se posera le problème de la réinitialisation de la barrière. En effet, il faudra s’assurer que la réinitialisation n’a pas lieu alors que certains threads n’ont pas encore franchi le point de synchronisation.
Approche de la conception :
Dans un premier temps, Master ne fournit des travaux qu’une seule fois (pas de fonctionnement en boucle). Dans un deuxième temps, nous rajouterons des barrières pour clairement identifier le début et la fin des travaux. Dans un dernier temps, nous ferons en sorte que Master et Worker effectuent leur travail en boucle.
Nous voulons faire en sorte que les Workers ne s’activent que lorsque Master a fourni ses travaux. Nous allons définir deux barrières :
Nous partirons des squelettes proposés dans les fichiers Barrier.java, Master.java, Worker.java et BarrierMain.java.
Questions
javac BarrierMain.java java BarrierMain 3 10
Nous voulons exécuter en boucle Master et Workers pour produire et traiter des travaux indéfiniment.
Mais avant de réaliser cette fonctionnalité, nous allons faire en sorte que Master et Worker démarrent en même temps et se terminent en même temps (afin de synchroniser Master et Worker sur le début et la fin de la boucle).
Nous allons donc rajouter deux nouvelles barrières :
Questions
javac BarrierMain.java java BarrierMain 3 10
Désormais, Master et Worker s’exécutent en boucle. Les méthodes run de Master et Worker sont donc identiques à ceci près qu’elles s’exécutent sans fin.
Les problèmes que nous allons devoir traiter portent sur la réinitialisation des barrières au cours des différentes boucles.
L’utilisation de la barrière se fera donc après avoir passé la barrière B3 et avant d’avoir passé la barrière B1.
Par soucis de simplication, nous conserverons toutefois les initialisations de barrières faites dans main (en plus de celles qui seront faites dans Master).
Questions
javac BarrierMain.java java BarrierMain 3 10