Exercice
TODO:
- Pour lecteur/rédacteur, la détection du premier/dernier est pertinent si on utilise des sémaphores, pas si on utilise des moniteurs
- afficher messages d'erreurs si protocole non respecté
1. Création d'un thread – synchronisation sur la fin du thread
- Lancer Eclipse et créer un projet Java
gloo.synchronisation
. - Créer une classe
EssaiThread
(qui contiendra une fonctionmain()
) qui étend la classejava.lang.Thread
et dont le constructeur reçoit un nom qu'il transmet au constructeur de la super-classe. - Surcharger la méthode
run()
pour :- afficher un premier message, préfixé de son nom, indiquant le lancement ;
- attendre un temps aléatoire entre 0 et 1 seconde (voir
Thread.sleep(long)
) ; - afficher un second message, lui aussi préfixé de son nom, indiquant la terminaison.
- Dans la fonction
main()
de cette classe, en affichant un message avant chaque étape et après la dernière :- créer une instance de votre classe ;
- lancer son exécution comme thread (voir
Thread.start()
) ; - attendre la fin de cette exécution (voir
Thread.join()
).
- Vérifier le bon fonctionnement.
2. Création d'un thread – synchronisation par moniteur
- Créer une classe
EssaiRunnable
:- qui implémente l'interface
java.lang.Runnable
; - qui possède un attribut
static
de typeObject
qui sera utilisé comme moniteur ; - qui a un constructeur prenant comme argument un nom et le mémorise en attribut ;
- dont la fonction
run()
:- affiche un premier message, préfixé de son nom, indiquant son lancement ;
- attend un temps aléatoire entre 0 et 1 seconde ;
- affiche un second message indiquant qu'il va réveiller le thread principal ;
- réveille via le moniteur (en utilisant
Object.notify()
) le thread principal ; - affiche un dernier message indiquant sa terminaison.
- qui implémente l'interface
- Dans la fonction
main()
de cette classe, en affichant un message avant chaque étape et après la dernière :- créer l'objet qui servira de moniteur ;
- créer une instance de votre classe ;
- lancer son exécution comme thread ;
- attendre le signal de sa fin sur le moniteur (en utilisant
Object.wait()
).
- Vérifier le bon fonctionnement.
3. Un producteur et un consommateur sans synchronisation
Le problème producteurs-consommateurs est un classique dans le domaine de la synchronisation de processus (ou threads).
Sa description générique est la suivante :
- un ou des producteurs produisent des données à destination d'un ou plusieurs consommateurs (le type de ces données est ici sans importance) ;
- pour transférer les données d'un producteur vers un consommateur, un objet intermédiaire, appelé classiquement boîte à lettres, est utilisé ;
- cette boîte a une capacité limitée, il faut donc gérer les problèmes de boîte pleine et de boîte vide, et éviter qu'un producteur écrase la donnée d'un autre ou que plusieurs consommateurs récupèrent la même donnée.
Nous allons examiner dans la suite différentes solutions à ce problème.
- Définir une classe
BoiteALettres
. Celle-ci servira d'intermédiaire entre un producteur de messages (ceux-ci seront des entiers) et un consommateur de ces messages.
Cette boîte à lettres utilisera un tableau de taille
entiers (valeur donnée au constructeur) pour stocker les messages reçus du producteur et non encore remis au consommateur. Ce tableau sera géré de manière circulaire (on recommence au début du tableau quand on est arrivé à la fin) et utilisera deux indices indiceDepot
et indiceRetrait
pour mémoriser la prochaine case où un message doit être déposé (respectivement : doit être extrait).
- Définir les attributs correspondants à cette description, les initialiser dans le constructeur.
- Définir les deux méthodes
void depot(int)
etint retrait()
offrant les services demandés ; la méthodeint retrait()
mettra0
à la place du message retiré, cela facilitera la détection des dysfonctionnements. - Créer une classe
Producteur
qui étend la classeThread
. Son constructeur recevra en argument une boite et le nombre de messages à y déposer. Sa méthoderun()
déposera (et affichera avant) dans la boite le nombre de messages prévu (des nombres entiers consécutifs à partir de 1) en attendant un temps aléatoire avant chaque dépôt. - Créer une classe
Consommateur
qui étend la classeThread
. Son constructeur recevra en argument une boite et le nombre de messages à extraire. Sa méthoderun()
retirera (et affichera) de la boite le nombre de messages prévu en attendant un temps aléatoire avant chaque retrait. Un message d'erreur sera affiché si un message extrait n'est pas strictement positif. - Créer une classe
Main
dont la méthodemain()
crée la boîte (de tailleTAILLE_BOITE
), le producteur et le consommateur, lance ces deux derniers et attend leur fin. Le nombre de messages sera fixé par une constanteNOMBRE_MESSAGES
. - Fixer les deux constantes
TAILLE_BOITE
etNOMBRE_MESSAGES
à10
, exécuter le programme. Que constatez-vous ?
4. Un producteur et un consommateur synchronisés par moniteur
- Définir une classe
BoiteALettresAvecMoniteur
, sous classe deBoiteALettres
. Surcharger les méthodesvoid depot(int)
etint retrait()
pour que les dépôts et les retraits soient en exclusion mutuelle via le moniteur associé à la boîte. Le consommateur devra être suspendu s'il n y a pas de nouveaux messages pour lui.
Les méthodes de la classe BoiteALettresAvecMoniteur
doivent appeler celles de BoiteALettres
en ajoutant simplement la synchronisation nécessaire pour obtenir un fonctionnement correct.
Concrètement, le tableau et les indices utilisés par BoiteALettres
doivent rester privés.
- Exécuter le programme, le résultat est-il satisfaisant ?
- Fixer la constante
TAILLE_BOITE
à2
, exécuter le programme, le résultat est-il satisfaisant ? Modifier votre code pour obtenir un résultat correct.
5. Deux producteurs et un consommateur synchronisés par moniteur
- Créer dans la méthode
main()
un second producteur utilisant la même boîte à lettres. Le consommateur devra donc extraire deux fois plus de messages. - Exécuter votre code. Selon la façon dont vous l'aurez écrit, vous obtiendrez un comportement satisfaisant ou pas. Si nécessaire, corriger votre code.
6. Deux producteur et un consommateur synchronisés par sémaphore
- Définir une classe
BoiteALettresAvecSemaphore
, sous classe deBoiteALettres
. Surcharger les méthodesvoid depot(int)
etint retrait()
pour synchroniser, en utilisant des sémaphores, les dépôts et les retraits.
Un sémaphore gère un nombre fixe de ressources. Quelles sont les ressources nécessaires au producteur ? Au consommateur ? Donc, combien de sémaphores ? avec quelle valeur initiale du compteur ?
- Modifier la méthode
main()
pour utiliser cette classe.
Votre programme va certainement montrer un fonctionnement satisfaisant si vous avez correctement utilisé les sémaphores ; pourtant, votre programme n'est pas correct. Pour essayer de mettre en évidence ce dysfonctionnement :
- agrandir votre taille de boîte (la constante
TAILLE_BOITE
) pour que tous les messages déposés par les producteurs puissent être stockés dans la boîte ; - supprimer le temps d'attente aléatoire entre chaque message déposé par les producteurs ;
- ajouter une attente de 0,5 seconde entre les deux instructions de la méthode
BoiteALettres.depot(int)
. - Observer de nouveau les messages récupérés par le consommateur :
- sont-ils corrects ?
- pouvez-vous identifier la cause du problème ?
- pourquoi ce problème est-il présent avec les sémaphores mais pas avec les moniteurs ?
- quelle solution proposez-vous pour résoudre ce problème ?
7. Lecteurs et rédacteurs
Le problème lecteurs-rédacteurs est aussi un classique dans le domaine de la synchronisation de processus (ou threads).
Sa description générique est la suivante :
- une information peut-être lue simultanément par beaucoup de lecteurs
- par contre, pour la mettre à jour, il faut imposer qu'il n'y ait qu'un seul rédacteur sans aucun lecteur.
Nous modéliserons ce problème de la façon suivante :
- une classe
Information
représentera la donnée accédé en lecture (par des lecteurs) ou en écriture (par des rédacteurs) ; - cette classe offrira les services suivants :
void accesLecture()
utilisée par un lecteur voulant accéder à l'information ;- soit cette dernière est disponible (0 ou des lecteurs sont présents, mais pas de rédacteurs), l'accès est alors accordé ;
- soit un rédacteur est présent : le lecteur est suspendu jusqu'à libération de l'information par le rédacteur ;
void finLecture()
utilisée par un lecteur signalant qu'il n'a plus besoin d'accéder à l'information ;void accesEcriture()
utilisée par un rédacteur voulant modifier l'information ;- soit cette dernière est disponible (pas de lecteurs, pas de rédacteurs), l'accès est alors accordé ;
- soit un lecteur ou un rédacteur est présent : le rédacteur est suspendu jusqu'à rendre possible l'accès ;
void finEcriture()
utilisée par un rédacteur signalant qu'il a terminé la modification de l'information ;
Éléments fournis :
- Classe
Information
, ses méthodes se contentent d'afficher des messages ; - Classes
Lecteur
etRedacteur
, sous classes deThread
; les constructeurs reçoivent une instance d'Information
, leur méthoderun()
demande un accès en lecture ou en écriture selon le cas, attend un temps aléatoire puis signale la fin de l'accès ; des messages sont affichés pour signaler ces étapes ; - Classes
FabriqueLecteurs
etFabriqueRedacteurs
, sous classes deThread
; leur méthoderun()
est en charge de créer les lecteurs (respectivement : les rédacteurs) avec une attente de temps aléatoire entre chaque ; une fois créé le nombre d'instances prévu, les fabriques attendent que tous les lecteurs (respectivement : rédacteurs) soient terminés ; - Classe principale dont la méthode
main()
crée les fabriques et attend leur terminaison ; les paramètres numériques de simulation y sont définis sous forme de constantes.
Le projet Eclipse contenant l'ensemble de ces classes est disponible via ce lien ; il ne reste qu'à compléter la classe Information
.
Une fois constaté le dysfonctionnement du système (par exemple : les rédacteurs accèdent à l'information alors que des lecteurs sont présents...), proposer une solution de synchronisation utilisant les moniteurs ou les sémaphores (il est préférable de ne pas mélanger les deux).
L'information est une ressource critique qui doit être réservée par le premier lecteur et libérée par le dernier, ou réservée par un unique rédacteur et libérée par lui. Il semble donc nécessaire de compter les lecteurs pour identifier le premier et le dernier.

© 2022-23 CentraleSupélec