1 of 94

PROGRAMMATION ORIENTEE OBJET AVANCEEJava Avancée 

Par F. Guerouate

1

2 of 94

Objectifs:

  • Définitions relatives à la programmation concurrente : les Threads.
  • La création/destruction des Threads. Ordonnancement des Threads.
  • La synchronisation des Threads. Le verrouillage des méthodes et des instructions (synchronized). Les moniteurs.
  • Principes méthodologiques pour la conception d'applications multithreads.
  • Présentation des modèles MVC
  • Développement à base des frameworks
  • Principes de conception et séparation des rôles
  • Le modèle MVC et les composants Java EE (JSP / Servlets / Beans, EJB)

2

3 of 94

  • Définitions relatives à la programmation concurrente : les Threads.

3

4 of 94

Introduction

Programmation multitâche:

Tous les systèmes d'exploitation actuels ont la possibilité d'effectuer plusieurs tâches en simultanée.

4

5 of 94

Introduction

  • En effet, lorsqu'un utilisateur consulte un site Internet, l'ordinateur effectue plusieurs tâches en même temps, comme gérer la communication Internet, analyser le contenu reçu, et gérer l'arrivée permanente de données.
  • Donc, chaque processus exécutée sur la machine est plus ou moins indépendant des autres.

5

6 of 94

Threads et processus

  • Un processus est un programme s'exécutant de manière indépendante des autres processus. Il possède une copie unique de ses propres variables.

6

7 of 94

  • Le thread, lui, partage les données avec les autres threads.

  • il est bien plus rapide de créer et de détruire les threads individuels que de créer des processus.

7

8 of 94

8

9 of 94

Le "faux multitâche" 

  • La plupart des ordinateurs n'ayant qu'un seul processeur. En effet, chaque thread dispose d'un temps de parole (temps minimal pendant lequel il va s'exécuter) après lequel il doit rendre la main au système pour laisser une chance aux autres threads de faire leur travail.

9

10 of 94

  • Sur une machine monoprocesseur, c'est le système d'exploitation qui alloue du temps d'utilisation du CPU (ou unité centrale de traitement, UCT, en anglais central processing unit, CPU) pour accomplir les traitements de chaque threads, donnant ainsi l'impression que ces traitements sont réalisés en parallèle.

10

11 of 94

  • Sur une machine multiprocesseur, le système d'exploitation peut répartir l'exécution sur plusieurs coeurs, ce qui peut effectivement permettre de réaliser des traitements en parallèle.

11

12 of 94

Un exemple de thread

  • Le navigateur qui nous sert à consulter des sites Web dispose d'une fonctionnalité intéressante qui consiste à télécharger plusieurs images d'une page en même temps. Le téléchargement et l'affichage d'une image correspond à un thread distinct.
  • Le navigateur peut choisir de démarrer plusieurs téléchargements simultanés et cela, en rapport avec le débit maximum de la connexion.

12

13 of 94

13

14 of 94

  • La création/destruction des Threads. Ordonnancement des Threads.

14

15 of 94

  • La classe java.lang.Thread et l'interface java.lang.Runnable sont les bases pour le développement des threads en java.

15

16 of 94

  • Le système d'exploitation va devoir répartir du temps de traitement pour chaque thread sur le ou les CPU de la machine. Plus il y a de threads, plus le système va devoir switcher. De plus, un thread requiert des ressources pour s'exécuter notamment un espace mémoire nommé pile. Il est donc nécessaire de contrôler le nombre de threads qui sont lancés dans une même JVM.

16

17 of 94

  • L'utilisation de la classe Thread est d'assez bas niveau. A partir de Java 5, le package java.util.concurrency propose des fonctionnalités de plus haut niveau pour faciliter la mise en oeuvre de traitements en parallèle et améliorer les performances de la gestion des accès concurrents.

17

18 of 94

La création des Threads

L'objet thread de Java

Pour créer des Threads…

  • Deux méthodes, un point commun :

« écrire une méthode run() qui fait

ce que doit faire le thread. »

    • Méthode 1 : Dériver une classe de Thread.
    • Méthode 2 : implémenter l’interface Runnable.

18

19 of 94

Méthode 1

La première méthode s’avère irréaliste étant donné que la plupart du temps, vos propres classes hériteront déja d’une autre classe et par conséquent, ne pourront pas hériter de plusieurs classes. En Java, l’héritage multiple n’est pas supporté.

Néanmoins, si votre classe n’hérite aucune autre, il est préférable de la faire hériter de la classe ‘Thread’.

19

20 of 94

  • lorsqu'on souhaite qu'une classe puisse se comporter comme un thread, il faut que :

Cette classe hérite de la classe Thread (méthode 1).

20

21 of 94

  • Le code correspondant a cette méthode

21

22 of 94

Exemple Méthode 1

  • class AfficheL extends Thread{
  • String txt;
  • int attente;
  • public AfficheL(String t, int p)
  • {
  • txt=t;
  • attente=p;
  • }
  • public void run()
  • {
  • for(int i=0;i<8;i++)
  • {
  • System.out.println(txt +i+"");
  • try{
  • sleep(attente);
  • }
  • catch(InterruptedException e){};
  • }}}

22

23 of 94

  • public class exemple1AfficheLettre {
  • public static void main(String[] args){
  • AfficheL a=new AfficheL("A",100);
  • AfficheL b=new AfficheL("B",200);
  • a.start();
  • b.start();
  • }

  • }

23

24 of 94

Exemple méthode 1

Le résultat de l’exécution:

A0 B0 A1 B1 A2 A3 A4 B2 A5 B3 A6 A7 B4 B5 B6 B7

24

25 of 94

Cette classe implémente l'interface Runnable, surcharge la méthode run et contient un champ de type Thread (méthode 2).�

25

26 of 94

  • Le code correspondant a cette méthode

26

27 of 94

Exemple methode2

  • class AfficheL2 implements Runnable{
  • String txt;
  • int attente;
  • public AfficheL2(String t, int p)
  • {
  • txt=t;
  • attente=p;
  • }

27

28 of 94

  • public void run()
  • {
  • for(int i=0;i<8;i++)
  • {
  • System.out.print(txt +i+" ");
  • try{
  • Thread.currentThread();//.sleep(attente);
  • Thread.sleep(attente);
  • }
  • catch(InterruptedException e){};
  • }}
  • }

28

29 of 94

  • public class exemple2Runnable {
  • public static void main(String[] args){
  • AfficheL2 a=new AfficheL2("A",100);
  • AfficheL2 b=new AfficheL2("B",200);
  • Thread a1=new Thread(a);
  • Thread b1=new Thread(b);
  • //System.out.println("Affichage de A");
  • a1.start();
  • //System.out.println("Affichage de B");
  • b1.start();
  • }}

29

30 of 94

Exemple méthode 2

Le résultat de l’exécution:

A0 B0 A1 B1 A2 A3 B2 A4 A5 B3 A6 A7 B4 B5 B6 B7

30

31 of 94

  • Voici un digramme objet représentant le cas de figure le plus courant où l’objet devant s’exécuter comme un thread implémente l’interface Runnable et hérite de sa classe parent.

31

32 of 94

Constructeurs de thread

32

33 of 94

Quelques méthodes

33

34 of 94

Propriétés des threads

  • Les threads peuvent être dans plusieurs états différents :

Nouveau : Le thread a été crée avec new mais n'est pas encore activé

Exécutable : La méthode start a été appelée mais cela ne signifie pas que le thread est exécuté. Cela va dépendre du système d'exploitation qui doit lui donner une fenêtre d'exécution.

34

35 of 94

Bloqué : Plusieurs évènements peuvent bloquer un thread. La méthode sleep lorsqu'elle est appelée, met le thread dans cet état.

Mort : Lorsque la méthode run est terminée, le thread est considéré comme mort.

35

36 of 94

Cycle de vie d’un thread

1. Au début, un thread occupe un espace mémoire, mais il n’a pas été démarré. Il n’est pas encore vivant et un appel de sa méthode isAlive retournera faux.�2. Ensuite on appelle la méthode « start() » du thread ce qui le fait « naître ». Il devient vivant et commence l’exécution de sa méthode « run() ». Un appel à sa méthode isAlive retournera vrai.

3. Pendant qu’il est en exécution, un thread peut « laisser la main » avec un appel de la méthode yield.

36

37 of 94

4. Pour passer en mode « non exécutable » (non runnable), il faut qu’une des trois conditions suivantes soit remplie :

    • un appel de la méthode « sleep(int) »
    • un appel de la méthode wait()
    • un accès I/O bloqué.

5. Pour revenir en mode exécutable selon la raison qui les a rendus non exécutables :

    • sleep : le nombre de milliseconde de sommeil est terminé.
    • wait : l’objet après qui le thread attend, effectue un appel de la méthode « notify » ou « notifyAll »
    • I/O bloqué : l’opération d’I/O est terminé.

37

38 of 94

6. Pour mourir, un thread doit terminer sa méthode « run » ou propager une exception hors de celle-ci.

38

39 of 94

Gérer la répartition du temps entre les threads

  • Lorsque plusieurs threads fonctionnent en même temps, il est parfois nécessaire de contrôler la façon dont le temps du processeur est réparti entre chacun d'eux. (Exemple TP1 exercice 4).

39

40 of 94

Planification d’un thread

  • La plupart des systèmes d’exploitation permettent la programmation multithread, mais tous ne le font pas de la même façon.
  • En effet, java pour Solaris ne permet pas à deux threads de priorité égale de se battre pour le contrôle, c’est alors au programmeur de gérer les passages de mains entre des threads de priorité égales.

40

41 of 94

  • La priorité d’un thread est un nombre qui lui est assigné et qui permet d’identifier la part de ressource de traitement auquel il aura droit dans un système découpant le temps ou quel thread sera en exécution dans un système de préemption (expropriation ).
  • La priorité s’étend en JAVA de MIN_PRIORITY (1) à MAX_PRIORITY(10).
  • La méthode yield permet à un thread de passer la main. Sur une plate-forme qui découpe le temps, cela permet de ralentir un thread et libérer des ressources de traitement.

41

42 of 94

  • La méthode join() permet d’attendre la mort d’un thread avant de continuer le traitement. Le thread à partir duquel la méthode a été appelée est celui qui doit mourir pour que le traitement continue.
  • Voyons maintenant le tout dans deux petits exemples. Le premier CHorlogerie étend la classe Thread. Cinq threads sont créés et démarrés. Ensuite, le thread principal (celui du départ qui exécute le « main ») attend la mort du thread no 4 ( T[4].join() ).

42

43 of 94

43

public class Horlogerie {

public static void main(String Args[]) throws InterruptedException{

System.out.println("Min: "+Thread.MIN_PRIORITY);

System.out.println("Max: "+Thread.MAX_PRIORITY);

Chrono T[]=new Chrono [5];

T[0]=new Chrono(9,"Tortue");

T[1]=new Chrono(6,"Poisson");

T[2]=new Chrono(3,"Chat");

T[3]=new Chrono(2,"Cheval");

T[4]=new Chrono(1,"Tigre");

44 of 94

44

for(int i=0;i<5;i++)

System.out.println(T[i].getName() + "status: "+T[i].isAlive());

for(int i=0;i<5;i++)

T[i].start();

for(int i=0;i<5;i++)

System.out.println(T[i].getName() + "status: "+T[i].isAlive());

T[4].join();

System.out.println("=============================");

for(int i=0;i<5;i++)

T[i].Arret();

45 of 94

45

class Chrono extends Thread{

private int compte;

private int attent;

private boolean continuer;

public Chrono(int vitesse,String Nom){

super(Nom);

setPriority(1);

if(Math.abs(vitesse)>10)

vitesse=10;

attent=Math.abs(vitesse)*1000;

compte=0;

continuer=true;

}

46 of 94

46

public void run(){

while(continuer && compte<10)

{

System.out.println(getName()+" : "+ compte);

compte++;

long T=System.currentTimeMillis();

while(System.currentTimeMillis()-T<attent);

}

System.out.println(getName()+" est mort ******");

}

public void Arret(){ continuer=false;}

}

47 of 94

47

Min: 1

Max: 10

Tortuestatus: false

Poissonstatus: false

Chatstatus: false

Chevalstatus: false

Tigrestatus: false

Tortue : 0

Poisson : 0

Chat : 0

Cheval : 0

Tortuestatus: true

Poissonstatus: true

Chatstatus: true

Chevalstatus: true

Tigrestatus: true

Tigre : 0

Tigre : 1

Cheval : 1

Tigre : 2

Chat : 1

Tigre : 3

Cheval : 2

48 of 94

48

Tigre : 4

Tigre : 5

Chat : 2

Cheval : 3

Poisson : 1

Tigre : 6

Tigre : 7

Cheval : 4

Tigre : 8

Chat : 3

Tortue : 1

Tigre : 9

Cheval : 5

Tigre est mort ******

=============================

Chat est mort ******

Poisson est mort ******

Cheval est mort ******

Tortue est mort ******

49 of 94

  • Cette méthode a l’avantage d’être simple, mais a le désavantage que la classe contenant la méthode run doit hériter de la classe Thread. Comme l’héritage multiple n’existe pas, comment procéder si on ne veut pas que la classe hérite de Thread ? C’est alors que l’on utilise la deuxième méthode qui consiste à implémenter l’interface Runnable.

L’interface Runnable, force la définition dans la classe d’une méthode : public void run()

49

50 of 94

  • Pour simplifier l’utilisation et uniformiser, il est conseiller d’ajouter des méthodes telles join, isAlive, start, etc. à la classe Runnable pour l’utiliser comme un objet de type Thread.

50

51 of 94

  • Voyez l’exemple suivant qui démontre cette méthode :

51

52 of 94

52

53 of 94

  • La synchronisation des Threads. Le verrouillage des méthodes et des instructions (synchronized). Les moniteurs.

53

54 of 94

  • Dans de nombreux cas, les threads partagent les mêmes objets et par conséquent, la modification de ces objets par un thread pourrait avoir des conséquences fâcheuses pour les autres threads notamment si ceux-ci modifient un objet en même temps.�
  • Java propose un mécanisme de verrouillage des objets par un thread de telle sorte qu'aucun autre thread ne puisse le modifier aussi.
  • Une autre façon de partager des variables est d’utiliser des variables statiques.

54

55 of 94

Deux exemples un seul concept

  • Exemple1: un compte bancaire sans verrouillage des données

55

56 of 94

Remarque

  • En Java on ne protège pas directement les données d’un objet, mais plutôt la manière d’accéder à ces données.

  • Il n’y a pas 1 verrou par méthode synchronisée d’un objet, mais bien 1 verrou pour toutes les méthodes synchronisées de cet objet.

  • La gestion des clés et des verrous est réalisée par la JVM

56

57 of 94

Mot clé synchronised

  • Pour permettre au thread 1 de ne pas être interrompu, on utilise le mot clé synchronised comme modificateur de la méthode qui ne doit pas être interrompue :�

57

  • Dans ce cas, la méthode ne sera pas interrompue.

58 of 94

  • Exemple2: On se place dans le cadre d’une application bancaire où deux personnes accèdent simultanément au même compte. Chaque personne est modélisée par un thread. Les personnes se partagent un objet une instance de la classe Account définie ci-dessous:

58

59 of 94

59

60 of 94

  • On remarque que la méthode withdrawMoney (retire argent) vérifie le solde du compte avant de faire le retrait. Cependant on obtient avec un compte initialement a 60, l’exécution suivante :

60

61 of 94

  • Le thread toto appel alors withdrawMoney avec l’argument 20, avant que tata ait effectué le retrait. Le thread toto vérifié alors que la valeur de amount est bien inferieur ou égale a celle de value.
  • On est alors dans une position ou les deux threads peuvent simultanément faire un retrait.

61

62 of 94

Solution : synchronized

  • Pour résoudre ce problème il faut que le test amount > value et l’instruction value = value - amount soient réalisé par le même thread sans être interrompu, autrement dit de manière atomique.
  • public synchronized boolean withdrawMoney(int amount) { ... }
  • Et on obtient alors l’exécution suivante :

62

63 of 94

63

64 of 94

  • Le mot clé synchronized peut également s’utiliser dans le corps de la méthode withdrawMoney.

64

65 of 94

Verrous d'objets (Les moniteurs)

  • Chaque fois qu'une méthode ou un bloc de code est affecté du mot clé synchronized, la méthode est verrouillée et aucun autre objet ne peut appeler cette méthode. Le Thread pose un verrou qu'il enlèvera en sortant de la méthode.
  • Ce système de verrouillage soulève tout de même un problème si jamais le thread verrouillé effectue une action qui se prolonge dans le temps, voire infiniment. La conséquence est que tous les autres threads n'ont plus aucune chance de s'exécuter. Pour pallier à cet inconvénient, il y a la méthode wait.

65

66 of 94

wait et notify

  • wait Lors de l'appel à la méthode wait, le thread enlève son verrou et vient se mettre en liste d'attente pour l'exécution. L'objet étant déverrouillé, les autres threads peuvent alors s'exécuter.
  • notify et notifyAll Donc, si un thread appelle sa méthode wait , il n'a aucun moyen de se débloquer lui-même. Il doit donc compter sur les autres threads pour appeler la méthode notifyAll ou notify.

66

67 of 94

Remarque :

  • Il peut être dangereux d'appeler la méthode notify car vous ne savez pas quel thread sera débloqué. Il est plus approprié d'employer la méthode notifyAll. L'endroit le plus approprié pour appeler cette méthode est dans l'objet qui est susceptible de changer la donné pour les threads mis dans la file d'attente avec wait.

67

68 of 94

Exemple: producteur consommateur

  • On se place dans le cas du programme multithreadé suivant : on dispose de deux threads l’un nommé producteur qui écris dans un objet, l’autre nommé consommateur qui lis dans le même objet qui est donc partage avec le producteur.
  • Si le producteur écris plusieurs fois de suite sans que le consommateur lise, alors les valeurs sont perdues.

68

69 of 94

69

70 of 94

L’objet partagé

  • Voici une description possible de l’objet partagé sans synchronisation producteur/consommateur

70

71 of 94

  • L’exécution n’est pas valide car si le résultat n’est pas disponible la méthode read renvoie -1, il faut alors systématiquement invoquer read via while (read() == -1) { } .
  • Une autre solution qui ne marche pas non plus serait dans chaque méthode d’attendre le changement de valeur du paramètre available. En effet les méthode étant synchronized elles mettent un verrou sur l’instance de sharedObject.

71

72 of 94

  • La manière correcte d’implémenter des notions est d’utiliser les méthodes wait et notifyAll.
  • L’appel a la méthode wait suspend l’exécution d’un thread jusqu’à ce qu’un appel à notifyAll soit effectue par un autre thread.
  • Voici alors le code de SharedObject :

72

73 of 94

73

74 of 94

Résumé

  • Les méthodes d'instances de la classe Thread��Java permet d'arrêter (stop), de faire attendre (sleep, yield), d'interrompre (interrupt), de changer la priorité(setPriority), de détruire(destroy) des threads à travers les méthodes de la classe Thread.��Java permet à des threads de "communiquer" entre eux sur leur état (join, notify, wait)

74

75 of 94

  • Principes méthodologiques pour la conception d'applications multithreadées

75

76 of 94

  • Dans le cas où votre programme contient une grande quantité de threads, il peut être utile de manipuler ces threads par groupe.

  • On obtient le groupe d’un thread par l’appel a la méthode getThreadGroup().

76

77 of 94

Groupes de threads

  • De plus, les groupes de thread peuvent contenir d’autres groupes de threads et ainsi former un arbre.

  • Il existe des constructeurs de Thread pour directement créer un thread dans un groupe :
    • public Thread(ThreadGroup group, Runnable runnable)

77

78 of 94

  • ThreadGroup(String name)
    • nouveau groupe, son parent est le groupe courant

  • ThreadGroup(ThreadGroup parent, String name)
    • nouveau groupe, son parent est spécifié�

78

79 of 94

  • Reprenons l'exemple du navigateur internet téléchargeant des images. Lorsque vous cliquez sur Arrêter, le navigateur doit stopper le téléchargement des images.

  • Si chaque image se télécharge par l’intermédiaire d'un thread et que tous, font partie d'un groupe, nous disposons d'un moyen efficace de les stopper tous.

79

80 of 94

Java.lang.ThreadGroup

Utilisez le constructeur suivant :

ThreadGroup g = new ThreadGroup("WebImages0047654");

Pour ajouter des threads à ce groupe :�Thread th = new Thread (g, "Image1");

80

81 of 94

Les groupes permettent de facilement manipuler de manière simultanée tous les éléments du groupe.

  • int activeCount() : nb threads actifs dans le groupe

  • ThreadGroup getParent() : groupe parent

81

82 of 94

  • Interrompre les threads d'un groupe
  • Pour interrompre tous les threads d'un groupe, il suffit d'appeler la méthode interrupt du groupe :
    • g.interrupt();

  • Un groupe peut posséder des groupes enfants. Le fait d'interrompre les threads du groupe parent, interromps également les membres des groupes enfants.

82

83 of 94

83

84 of 94

84

85 of 94

85

86 of 94

86

87 of 94

  • Semaphore
    • Un sémaphore intervient donc dans le mécanisme de partage des ressources disponibles, qu'elles soient uniques ou non

87

88 of 94

  • Lorsque vous arrivez dans un restaurant, vous constatez que toutes les tables sont prises, et n'ayant pas réservé, vous êtes dans l'obligation d'attendre qu'une table se libère. Cette situation peut être gérée par un sémaphore : ici le type de ressource à se partager est la table, le nombre de ressources est connu (par exemple 20 tables dans ce restaurant), et toutes les ressources sont malheureusement prises et aucune n'est disponible pour vous. Vous attendez donc qu'une ressource se libère pour la prendre, vous attendez une table pour enfin pouvoir dîner !

88

89 of 94

  • D'un point de vue conceptuel, un sémaphore agit comme une porte d'accès, un point d'entrée.
  • un sémaphore ferait parfaitement l'affaire pour remplacer le maître d'hôtel, et vous ne seriez qu'un thread parmi tant d'autres ! Ici le maître d'hôtel refuse ou non votre accès à une ressource qu'est une table, il joue un rôle de régulateur, en contrôlant le nombre de tables disponibles. Ce comportement est le même pour un sémaphore : chaque sémaphore contrôlant un type de ressource connaît à chaque instant le nombre de ressources de ce type disponibles et autorise ou refuse l'accès d'un thread à une de ces ressources.

89

90 of 94

Ainsi, un sémaphore est un objet possédant deux attributs :

  • l'un de ces deux attributs doit permettre de comptabiliser le nombre de ressources disponibles à un instant donné ;
  • l'autre attribut doit contenir le nombre maximal de ressources.

90

91 of 94

  • Si on reprend encore une fois l'exemple du restaurant, le sémaphore représentant le réceptionniste aura un de ses deux attributs à 20 (le nombre maximal de tables dans le restaurant) et l'autre à 0 (le nombre de tables disponibles, c'est-à-dire aucune)

91

92 of 94

  • Afin de communiquer avec le sémaphore, on dispose de deux procédures, que l'on appelle communément P et V. Ces deux procédures servent pour l'accès aux ressources : P permet ainsi de demander une ressource et V permet de libérer une ressource.

92

93 of 94

  • En Java, le semaphore est cree en utilisant la classe java.util.concurrent.Semaphore .

93

94 of 94

Ressources

  • https://www.jmdoudoux.fr/java/dej/chap-threads.htm#threads-1

94