SE205: Travaux Pratiques sur la Programmation Concurrente en Java

Laurent Pautet (pautet@telecom-paristech.fr)

Index


1 TP de Concurrence en Java

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. Vous pouvez accéder à la documentation de l’API Java en suivant ce lien. Notamment la documentation sur les semaphores de Java se trouve ici. Le support de cours sur les threads se trouve ici. Vous trouverez dans cette archive compressée l’intégralité des sources.

Pour décompresser, utiliser GNU tar:

tar zxf src.tar.gz

Le TP n'est pas à rendre.


1.1 Création de processus et terminaison par ordre de création

On considère le fichier BoundedBufferMain.java. Ce programme crée des producteurs et des consommateurs qui échangent des données en utilisant un tampon partagé, comme ce fut le cas pour la version en C faite précédemment. On se concentre en premier lieu sur la création des producteurs et des consommateurs.

Les producteurs se trouvent décrits dans la classe Producer du fichier Producer.java et produisent des données (entiers) dans un tampon circulaire décrit dans la classe BoundedBuffer que l’on va protéger contre les accès concurrents. Des consommateurs se trouvent quant à eux dans la classe Consumer du fichier Consumer.java et consomment les données produites de la même manière.

Les accès des producteurs et des consommateurs pourront se faire suivant plusieurs sémantiques. Ils pourront être bloquants, non-bloquants, ou temporisés, c’est à dire qu’en cas de blocage, celui ne pourra pas excéder un temps donné.

Producteurs et consommateurs adoptent un comportement périodique. Toutes les périodes à partir d’un instant commun de démarrage startTime spécifié dans la classe Utils du fichier Utils.java, ils produisent ou consomment des données puis attendent la période suivante pour recommencer. Par exemple, les producteurs se réactivent toutes les startTime + n * producerPeriodn représente le nombre d’activations.

Plus généralement, les paramètres de configuration se trouvent spécifés dans la classe Utils du fichier Utils.java. Le nombre de producteurs (nProducers, celui de consommateurs (nConsumers), la taille du tampon circulaire (bufferSize), la sémantique des accès à celui-ci (semantics) ou les périodes des producteurs (producerPeriod) et des consommateurs (consumerPeriod) sont configurés grâce à un fichier de scenario dont le nom est passé sur la ligne de commande.

Compléter BoundedBufferMain pour créer autant de producteurs et de consommateurs que demandés et pour ensuite attendre la terminaison des producteurs et des consommateurs. Vous pouvez utiliser le fichier de scénario test–00.txt pour vérifier que vous créez le nombre correct de producteurs et de consommateurs.


1.2 Tampon protégé contre accès concurrents (java natif/bloquants)

Nous allons implanter un tampon circulaire protégé contre les accès concurrents dans la classe NatBoundedBuffer du fichier NatBoundedBuffer.java. Pour ce faire, on pourra utiliser l’implantation d’un tampon circulaire non-protégé qui est fourni dans la classe BoundedBuffer du fichier BoundedBuffer.java. Noter qu’à la différence de la version C, la classe NatBoundedBuffer hérite de la classe BoundedBuffer.

Complétez la classe NatBoundedBuffer sachant que vous ne devez utiliser que les constructions natives de Java c’est à dire synchronized, wait, notify et notifyAll.

Complétez les méthodes get et put afin qu’il offre les services suivants :

Testez le bon fonctionnement du tampon circulaire protégé en utilisant la classe BoundedBufferMain. Vous pourrez utiliser le scénario de test, test-00.txt. On notera les moments où les consommateurs et les producteurs sont supposés bloquer. Normalement, avec cette sémantique, toutes les données produites doivent être consommées.


1.3 Tampon protégé contre accès concurrents (java natif/non-bloquants)

Nous allons implanter dans la classe NatBoundedBuffer des accès concurrents au tampon circulaire protégé similaires aux précédents. Cependant, la sémantique de ces accès sera cette fois non-bloquante. Si l’opération ne peut pas avoir lieu immédiatement, elle ne bloque pas et retourne une valeur spéciale. Le producteur signalera par un code de retour binaire s’il a réussi à insérer immédiatement un élément dans le tampon. Le consommateur signalera par un pointeur nul ou non sur une donnée s’il a réussi à extraire immédiatement un élément dans le tampon. Le tampon devra toujours être protégé contre les accès concurrents.

Complétez les méthodes remove et add afin qu’elles offrent les services indiqués. Testez le bon fonctionnement du tampon circulaire protégé grâce à la classe BoundedBufferMain. Vous pourrez utiliser les scénarios de test, test-01.txt et test-02.txt. On vérifiera que les consommateurs et les producteurs ne restent pas bloqués et s’activent comme prévu. Un scénario a plus de producteurs que de consommateurs, le tampon se trouvera rempli et certains producteurs échoueront à déposer leurs données. Dans l’autre scenario, au contraire, ce sont les consommateurs qui échoueront à extraire des données.


1.4 Tampon protégé contre accès concurrents (java natif/temporisés)

Nous allons implanter dans la classe NatBoundedBuffer des accès concurrents au tampon circulaire protégé similaires aux précédents. Cependant, la sémantique de ces accès sera cette fois temporisée. Si l’opération ne peut pas s’effectuer immédiatement, elle bloque jusqu’à ce qu’elle puisse avoir lieu, mais n’attendra pas plus tard qu’une échéance donnée (qui correspond à une date ie un temps absolu). Si l’opération, elle retourne une valeur spéciale comme précédemment.

Complétez les méthodes poll et offer afin qu’elles offrent les services indiqués. Testez le bon fonctionnement du tampon circulaire protégé grâce à la classe BoundedBufferMain. Vous pourrez utiliser les scénario de test, test-03.txt et test-04.txt. On vérifiera que les consommateurs et les producteurs restent bloqués au plus le temps imparti et s’activent comme prévu. En l’occurrence, les accès doivent être débloqués au plus tard à l’échéance suivante.


1.5 Tampon protégé contre accès concurrents (sémaphore/bloquants)

On reprend la sémantique des accès concurrents précédents, mais cette fois ci nous allons réaliser une implémentation fondée sur les sémaphores. Au lieu d’utiliser les constructions natives de Java pour bloquer l’exécution, vous allez utiliser des sémaphores, qui sont fournis dans le paquetage Java java.util.concurrent.Semaphore. Vous modifierez la classe SemBoundedBuffer dans le fichier SemBoundedBuffer.java, et plus précisément les methodes get et put. Vous pourrez utiliser les mêmes scénarii que ceux de l’implémentation fondée sur les constructions natives.


1.6 Tampon protégé contre accès concurrents (sémaphore/non-bloquants)

On reprend la sémantique des accès concurrents précédents, ici celle des accès non-bloquants, mais en utilisant une implémentation fondée sur les sémaphores. Modifiez en conséquence la classe SemBoundedBuffer et notamment les méthodes remove et add. Vous pourrez utiliser les mêmes scénarii que ceux de l’implémentation fondée sur les constructions natives.


1.7 Tampon protégé contre accès concurrents (sémaphore/temporisés)

On reprend la sémantique des accès concurrents précédents, ici celle des accès temporisés, mais en utilisant une implémentation fondée sur les sémaphores. Modifiez en conséquence la classe SemBoundedBuffer et notamment les méthodes poll et offer. Vous pourrez utiliser les mêmes scénarii que ceux de l’implémentation fondée sur les constructions natives.