Vous êtes sur la page 1sur 109

SÉRIE FOCUS

Jean Charles Pomerol

Simultané, en temps réel et


Programmation distribuée en Java

Fils, RTSJ et RMI

Badr Benmammar

Publié pour la première fois en 2018 en Grande-Bretagne et aux États-Unis par ISTE Ltd et John Wiley & Sons, Inc.

ISTE Ltd John Wiley & Sons, Inc.


27-37, chemin St George 111, rue River
Londres SW19 4EU Hoboken, NJ 07030
Royaume-Uni États-Unis

www.iste.co.uk www.wiley.com
© ISTE Ltd 2018

Numéro de contrôle de la Bibliothèque du Congrès : 2017957888

Données de catalogage avant publication de la British Library


Une notice CIP pour ce livre est disponible à la British Library
ISSN 2051-2481 (imprimé)
ISSN 2051-249X (en ligne)
ISBN 978-1-78630-258-8

Contenu

Liste des acronymes. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . vii

Introduction . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . ix

Chapitre 1. Introduction aux threads en Java . . . . . . . . . . . . . . . . . 1


1.1. Processus contre threads. . . . . . . . . . . . . . . . . . . . . . . . . . . . 1
1.2. Calcul simultané. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 2
1.3. Création de fil. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 3
1.4. Types de fil . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 4
1.5. Monotâche contre multitâche. . . . . . . . . . . . . . . . . . . . . . . . . . . 5
1.6. Différents états d'un thread. . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.7. Cycle de vie d'un thread. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 13
1.8. Quelques notes concernant les fils . . . . . . . . . . . . . . . . . . . . . . . . 16
1.8.1. Deux threads sans utiliser sleep . . . . . . . . . . . . . . . . . . . . . 16
1.8.2. Allocation de temps entre deux threads. . . . . . . . . . . . . . . . . . 17
1.8.3. Priorité entre les threads. . . . . . . . . . . . . . . . . . . . . . . . . . 19
1.9. Programmation d'une tâche : Timer et TimerTask . . . . . . . . . . . . . . . . . 21
1.9.1. En spécifiant un délai initial . . . . . . . . . . . . . . . . . . . . . . . 21
1.9.2. Avec un délai initial et une périodicité. . . . . . . . . . . . . . . . . . . 23

Chapitre 2. Synchronisation des threads . . . . . . . . . . . . . . . . . . . . . . . 27


2.1. Synchronisation à la fin : méthode join() . . . . . . . . . . . . . 27
2.2. Ressource en exclusion mutuelle : modificateur synchronisé . . . . . . . . . . 30
2.3. Variables partagées : classe interne . . . . . . . . . . . . . . . . . . . . . . . . 33
2.4. Le problème des exclusions mutuelles. . . . . . . . . . . . . . . . . . . . . 35
2.5. Bloc synchronisé. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 36
2.6. Méthode d'instance synchronisée . . . . . . . . . . . . . . . . . . . . . . . . 41
2.7. Variables partagées : variable de classe . . . . . . . . . . . . . . . . . . . . . . . . 43
2.8. Synchronisation entre les threads. . . . . . . . . . . . . . . . . . . . . . . 45
2.8.1. Attendez et notifiez tout . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 45
2.8.2. Attendez et notifiez. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 48
2.9. Modèle classique producteur-consommateur . . . . . . . . . . . . . . . . . . . . . 51
2.10. Sémaphore en Java. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 54
2.10.1. Avant Java 1.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 55
2.10.2. Après Java 1.5 . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 57

Chapitre 3. Systèmes temps réel et Java temps réel . . . . . . . . . . . 61


3.1. Systèmes en temps réel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.1.1. Définition . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 61
3.1.2. Exemples de systèmes d'exploitation temps réel . . . . . . . . . . . . . . . . 62
3.1.3. Types de temps réel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . 62
3.1.4. Architecture . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 63
3.1.5. Ordonnance de tâche avec priorités . . . . . . . . . . . . . . . . . . . . . . 63
3.2. Java en temps réel. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 65
3.2.1. RTSJ (spécification temps réel pour Java) . . . . . . . . . . . . . . . 65
3.2.2. Implémentations. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 67

Chapitre 4. Programmation distribuée en Java . . . . . . . . . . . . . . . . 71


4.1. Définition d'une application distribuée . . . . . . . . . . . . . . . . . . . . 71
4.2. Communication dans une application distribuée . . . . . . . . . . . . . . . . . 72
4.2.1. Communication bas niveau : socket . . . . . . . . . . . . . . . . . . . . 72
4.2.2. Communication de haut niveau : middleware . . . . . . . . . . . . . . . . 89

Annexe . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 127

Bibliographie. . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 155

Index . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . . 157

Acronymes

Interface de programmation d'applications API


Interface native CNI Cygnus
Architecture CORBA Common Object Request Broker

Modèle d'objet de composant distribué DCOM

Collecte de déchets distribuée DGC

FIFO premier entré, premier sorti

GC Garbage Collector

Collection de compilateurs GCC GNU

Compilateur GCJ GNU pour Java

GNU GNU n'est pas Unix

Protocole inter-ORB Internet IIOP

Protocole Internet IP

J2SE Java 2 édition standard

J2EE Java 2 Édition Entreprise

J2ME Java 2 Micro Édition

Kit de développement JDK Java SE

Interface native Java JNI

Protocole de méthode distante Java JRMP

Extension de socket sécurisé Java JSSE

Machine virtuelle Java JVM

Kilo VM KVM

Interconnexion des systèmes ouverts OSI

Protocole de plafond de priorité PCP

Protocole d'héritage de priorité PIP

Invocation de méthode distante RMI

Compilateur RMIC RMI

Appel de procédure distante RPC

Couche de référence distante RRL

Spécification RTSJ temps réel pour Java

Couche de sockets sécurisée SSL

Protocole de contrôle de transmission TCP

Sécurité de la couche de transport TLS

Protocole de datagramme utilisateur UDP

URL Uniform Resource Locator

WinCE Windows Embedded Compact


Introduction

Ce livre constitue une introduction au temps réel et distribué


calcul simultané , utilisant le langage orienté objet Java comme outil de support
pour décrire les algorithmes. Il décrit notamment la synchronisation
mécanismes (en coopération et en compétition) et partage de données
mécanismes (classe interne, variables de type statique) entre les threads en Java.
Nous discutons ensuite de l'utilisation de Java pour les applications temps réel. Par la suite, un
la présentation de RTSJ
  (Real - Time Specification for Java) est également introduite
dans ce livre. Enfin, une présentation de l'informatique distribuée peut également être
trouvé. Nous nous concentrons en particulier sur la communication de bas niveau utilisant TCP
Sockets et communication de haut niveau à l'aide de Java RMI (Remote Method
Invocation) middleware. Le livre contient également une annexe comprenant un ensemble
d'exercices pratiques d'application en rapport avec le thème du livre.
La connaissance du langage Java est un pré-requis pour bien comprendre ce
livre.

Introduction aux threads en Java


1.1. Processus contre threads

Le système d'exploitation est chargé d'allouer les ressources nécessaires


(mémoire, temps de traitement, entrées/sorties) aux processus et s'assurer qu'ils
ne pas interférer les uns avec les autres (isolement) [TAN 01].

Ce principe est illustré dans l'exemple suivant dans lequel la variable a


est défini dans deux classes différentes. Lors de l'exécution des deux classes, les deux
les zones mémoire allouées à cette variable sont complètement isolées.

Illustration 1.1. Isolation entre les processus

2 Programmation simultanée, temps réel et distribuée en Java

 
La plupart des systèmes d'exploitation offrent une distinction entre :
– Processus lourds : soi-disant complètement séparés d'un
une autre.
– Processus légers (threads) : qui partagent un espace mémoire (ainsi
que d'autres ressources) en commun.

réEFINITION 1.– Un thread est une chaîne de code capable de s'exécuter parallèlement
d'autres processus. Les threads ne s'exécutent pas en même temps mais utilisent plutôt
temps partagé, c'est pourquoi il est important qu'un fil donne toujours aux autres un
chance d'exécution.

Le schéma ci-dessous montre les temps d'exécution et de latence pour quatre


fils différents.

Illustration 1.2. Temps d'exécution et de latence de quatre threads différents

1.2. Informatique simultanée

Un langage de programmation concurrent doit permettre ce qui suit :


– création de fils ;
– partage de données entre threads ;
– synchronisation entre threads : contrôle de l'ordre d'exécution des tâches
selon les deux modèles suivants :

Introduction aux threads en Java 3

 
- Synchronisation des compétitions : lorsque plusieurs threads utilisent le
même ressource. Il doit alors y avoir un système d'exclusion mutuelle afin de
éviter que les processus n'interfèrent les uns avec les autres.
- Synchronisation de la coopération : lorsqu'un thread attend qu'un autre
terminer l'exécution avant de commencer sa propre exécution.

1.3. Création de fil

Il existe deux manières de créer un thread en Java :


– Une classe dérivée de java.lang.Thread :
- Le java.lang.Thread implémente la classe Runnable.
- Le thread étend l'objet implémente la classe publique Runnable.
- La classe Thread doit implémenter la méthode run().
- La classe fille hérite de la méthode run().
– Une classe qui implémente l'interface Runnable :
- La classe doit implémenter la méthode run().
Maintenant, une question se pose : quelle solution doit-on choisir ?
– Méthode 1 : sous-classement de Thread :
- Lors de la parallélisation d'une classe qui n'hérite pas d'une autre classe
(classe autonome).
- Remarque : héritage simple en java.
- étend Thread ou implémente Runnable, dans les deux sens.
– Méthode 2 : implémenter Runnable :
- Lorsqu'une superclasse est imposée.
- Exemple, cas impliquant des applets :
la classe publique MyThreadApplet étend l'applet implémente Runnable { }

- Seuls les outils Runnable sont valides.

4 Programmation simultanée, temps réel et distribuée en Java

 
Regardons le code :
– Méthode 1 : sous-classement de Thread
la classe A étend le fil {
A ( ) {...} // Le constructeur
...
public void run ( ) {
... // Ce que fait le Thread
}
}
A p1 = nouveau A( ); // Création du fil p1
p1.start(); // Démarre le thread et exécute p1.run()

– Méthode 2 : classe qui implémente Runnable


la classe B implémente Runnable {
B ( ) { ...} // Constructeur
...
public void run() {
... // Ce que fait le Thread
}
}
B p = nouveau B( );
Thread p2 = nouveau Thread(p);
...
p2.start(); // Démarre le thread et exécute p.run()

1.4. Types de fils

Deux types de fil existent :


– Threads utilisateur : l'activité de ce type de thread est limitée dans le temps, c'est-à-dire
son scénario est un processus qui se termine au bout d'un certain temps. La JVM
fonctionne tant qu'il y a des threads utilisateur en cours d'exécution.
– Threads démons : threads qui s'exécutent en arrière-plan tant que
programme est en cours d'exécution. Les threads démons ne sont là que pour servir les threads utilisateur.
La JVM s'arrête s'il n'y a que des démons.

Introduction aux threads en Java 5

 
Exemples : coloration de la syntaxe dans les éditeurs, ramasse-miettes
(Détruire JavaVM), etc.
– public final boolean isDaemon() dans la classe Thread pour déterminer le
type de fil.
– public final void setDaemon (booléen) de la classe Thread indique
si le thread sera un démon ou non (thread utilisateur par défaut).
– Il doit être appelé avant que le thread ne démarre à l'aide de la commande start().
– Utilisez setDaemon dans le constructeur.

Exemple : thread d'horloge exécuté en arrière-plan.

La classe d'horloge étend Thread { public clock () { setDaemon (true); } public void run () { while (true) { try {Th

public static void main(String arg[]){


A p1 = nouveau A( );
p1.start();
}// fin principale
}// fin de classe

1.5. Monotâche contre multitâche

Pour illustrer le multitâche, commençons par présenter une exécution monotâche.


Dans l'exemple suivant, un perroquet (Parrot0.java) converse en monotâche avec
le programme principal (ChatAndLaunchTheParrot0.java):
class ChatAndLaunchTheParrot0 { public static void main(String args[]) { Parrot0 parrot = new Parrot0 ("coco",

6 Programmation simultanée, temps réel et distribuée en Java blabla(); blabla(); perroquet.run (); for (int n=0; n<3; n+
 

}// end main private static void blabla() { System.out.println("blabla"); }

}// fin de classe

class Parrot0 { chaîne privée cri = null ; int privé fois = 0 ; public Parrot0 (String s, int i) { cri = s; fois = je ; } publ

} // terminer l'exécution
} // classe de fin

L'exécution du code précédent donne les résultats suivants : blabla blabla

Introduction aux threads en Java 7 coco coco coco coco blabla blabla blabla
 
Rien de spécial, le programme principal parle, passe à la course du perroquet et
l'exécution se termine par les trois blabla du programme primaire.

Pour effectuer le multitâche, une modification du code précédent est


nécessaire. Nous allons le réaliser de deux manières différentes.

– Extension de la classe Thread :

class ChatAndLaunchTheParrot2{ public static void main(String args[]) {

Parrot2 perroquet = nouveau Parrot2 ("coco",10);


perroquet.start(); for (int n=0; n<10; n++) { try { Thread.sleep(1000); } catch(InterruptedException e) { } blabla()

la classe Parrot2 étend Thread { chaîne privée cri = null ; int privé fois = 0 ;

8 Programmation concurrente, temps réel et distribuée en Java public Parrot2 (String s, int i) { cri = s; fois = je ; } publi
 

L'exécution du code précédent donne les résultats suivants :

coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla

Introduction aux threads dans Java 9

 
coco
blabla
coco
blabla

– Implémentation de l'interface Runnable :


class ChatAndLaunchTheParrot1{ public static void main(String args[]) {

Parrot1 objectParrot = new Parrot1 ("coco",10); Thread ThreadParrot = nouveau Thread (objectParrot); ThreadP

}
la classe Parrot1 implémente Runnable { chaîne privée cri = null; int privé fois = 0 ; public Parrot1 (String s, int

10 Programmation simultanée, temps réel et distribuée en Java Thread.sleep(1000); } catch(InterruptedException e) {


 
}

L'exécution du code précédent donne les résultats suivants :

blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco
blabla
coco

– run() est la méthode à redéfinir pour placer la fonction du Thread.


– start() est une méthode qui autorise le Thread à s'initialiser, et donc
l'exécution de la course. Tout ce qu'il fait est d'initialiser le Thread et (sans bloquer le
appelant Thread) n'attend pas que run termine son exécution.

Introduction aux threads en Java 11

 
– Il revient immédiatement à l'exécution du Thread appelant. La course
méthode du Thread s'exécute en même temps que les autres Threads
(multitâche).
– start() autorise le Thread à démarrer, mais pas nécessairement immédiatement.

L'exemple suivant simule une course à l'aide de threads. L'idée est de


simuler une course de 1000 m entre deux personnes prénommées Jean et Paul. Puisque c'est
nécessaires pour les faire fonctionner simultanément, chacun d'eux sera pris
par un fil.

Le programme suivant crée une classe Runner générale qui hérite de la


Fil de discussion.
classe Runner étend Thread {
public Runner (String str) { super(str); }

public void run() { // La valeur i est incrémentée au passage de la nième centaine de mètres for (int i =1; i<=10; i

sleep((int)(Math.random() * 1000)); } catch (InterruptedException e) {} } // fin pour System.out.println( getName


}// fin d'exécution
} // classe de fin

La classe de test, Race, est chargée de créer des instances Runner et


les lancer dans la course avec start().

class Race { public static void main (String[] args) { System.out.println("Passing: "); Coureur Jean = nouveau Cou

12 Programmation concurrente, temps réel et distribuée en Java Runner Paul = new Runner ("Paul"); Jean.start(); Paul
 

L'exécution de ce code donne les résultats suivants :

Qui passe:
100 m : Jean
100 m : Paul
200 m : Jean
200 m : Paul
300 m : Paul
300 m : Jean
400 m : Jean
500 m : Jean
600 m : Jean
400 m : Paul
700 m : Jean
500 m : Paul
600 m : Paul
800 m : Jean
900 m : Jean
1000 m : Jean
700 m : Paul
Jean termine !
800 m : Paul
900 m : Paul
1000 m : Paul
Paul termine !

Introduction aux threads en Java 13

 
1.6. Différents états d'un thread
Un thread peut être dans l'un des quatre états suivants :
– Nouveau : inclut tous les moments entre sa création (par un
constructeur) et l'appel de sa méthode start().
– Exécutable : immédiatement après l'appel de la méthode de démarrage.
– Bloqué : représentant les instants où le thread est en attente, par
exemple, lorsqu'il est suspendu par la méthode sleep (long) qui interrompt le
l'exécution du thread pendant un temps donné.
– Terminé : lorsque sa méthode run() se termine.

L'état d'un thread peut être spécifié par l'appel de sa méthode getState().

1.7. Cycle de vie d'un thread

Le cycle de vie d'un thread illustre qu'il peut être suspendu par :
– attendre la fin d'un sommeil ;
– attendre la fin d'un bloc entrée-sortie;
– en attente d'une synchronisation d'attente.

La figure 1.3 montre le cycle de vie d'un thread.

Illustration 1.3. Cycle de vie d'un thread

14 Programmation simultanée, temps réel et distribuée en Java

 
Pour la création de threads en Java, nous avons huit constructeurs
(surtaxe constructeur) [ORA 17a] :
- Fil de discussion();
– Thread (cible exécutable);
- Thread (cible exécutable, nom de chaîne);
– Thread (nom de chaîne);
– Thread (groupe ThreadGroup, cible exécutable);
– Thread (groupe ThreadGroup, cible exécutable, nom de chaîne);
– Thread (groupe ThreadGroup, cible exécutable, nom de chaîne, long
taille de la pile);
– Thread (groupe ThreadGroup, nom de chaîne).

NOTE .– De nombreux threads peuvent s'exécuter simultanément, il serait utile de


pouvoir les contrôler comme une seule entité : les suspendre, les arrêter,
etc.

Java offre cette possibilité via l'utilisation de groupes de threads :


java.lang.ThreadGroup [ORA 17b].

– Nous regroupons un ensemble nommé de threads.


– Ils sont contrôlés comme une seule unité.
– La JVM crée au minimum un groupe de threads nommé principal.
– Par défaut, un thread appartient au même groupe que celui qui l'a créé
(son père).
– getThreadGroup() : pour connaître son groupe de threads.
La classe ThreadGroup a deux constructeurs :
– ThreadGroup (nom de chaîne) ;
– ThreadGroup (ThreadGroup parent, nom de la chaîne).

Introduction aux threads en Java 15

 
Le code suivant crée un groupe de threads contenant trois threads :

ThreadGroup groupe1 = nouveau ThreadGroup ("GP1");


Thread p1 = nouveau Thread (groupe1,ème constructeur
"P1"); // 8 de threads
Thread p2 = nouveau Thread (groupe1, "P2");
Thread p3 = nouveau Thread (groupe1, "P3");

Le contrôle du ThreadGroup passe par l'utilisation de méthodes standards


qui sont partagés avec Thread : interrupt(), destroy().

Par exemple : appliquer la méthode interrupt() à GP1 signifie invoquer cette


méthode pour chaque Thread du groupe (P1, P2 et P3).

Le code suivant montre la création d'un arbre de thread :


// 11er constructeur de ThreadGroup
ThreadGroup groupe1 = new ThreadGroup("GP1");
Thread p1 = new Thread(groupe1, "P1");
Thread p2 = new Thread(groupe1, "P2");
Thread p3 = new Thread(groupe1, "P3");
// 2et constructeur de ThreadGroup
ThreadGroup groupe11 = new ThreadGroup(groupe1, "GP11");
Thread p4 = new Thread(groupe11, "P4");
Thread p5 = new Thread(groupe11, "P5");

ce qui nous donne l'arborescence suivante :

Illustration 1.4. Arbre de discussion


16 Programmation simultanée, temps réel et distribuée en Java
 
1.8. Quelques notes concernant les threads

1.8.1. Deux threads sans utiliser le sommeil

Dans le segment suivant, nous lancerons deux threads sans utiliser le sommeil,
le programme principal et un autre thread.

class ChatAndLaunchTheParrot7 { public static void main(String args[]) { Parrot7 perroquet = new Parrot7("coco",10);

}
la classe Parrot7 étend Thread { chaîne privée cri = null; int privé fois = 0 ; public Parrot7(String s, int i) { cri = s; fois =

} public void repeter() { System.out.println(cri); } public void run() { for (int n=0; n<fois; n++) repeter(); }

Exécution:

blabla
blabla
blabla
blabla
blabla
blabla

Introduction aux threads en Java 17

 
blabla
blabla
blabla
blabla
coco
coco
coco
coco
coco
coco
coco
coco
coco
coco

Il s'agit clairement d'une exécution multitâche mais le temps d'exécution est trop court pour
visualiser l'allocation du CPU entre les deux threads.

1.8.2. Allocation de temps entre deux threads


Afin de visualiser la répartition du temps entre deux threads, nous allons
doivent augmenter le nombre d'itérations à 5 millions en examinant
thread s'exécute par multiples de 500 000.

Le code est comme suit:


public class InfernalRace1 { public static void main(String[] args){ Runner A = new Runner("A"); Coureur B = nouveau

} // fin principale
} // termine la classe InfernalRace1

class Runner étend Thread { String nom;

18 Programmation concurrente, temps réel et distribuée en Java public Runner(String name) { super(name); this.nam
 

System.out.println("Runner " + nom + " exécute " + PedalStroke + " pédale


traits."); } // fin si } // fin tant que

}// fin d'exécution
}// termine la classe Coureur

Exécution:

Le coureur A effectue 500 000 coups de pédale.


Le coureur B effectue 500 000 coups de pédale.
Le coureur B effectue 1000000 coups de pédale.
Le coureur A effectue 1000000 coups de pédale.
Le coureur B effectue 1 500 000 coups de pédale.
Le coureur A effectue 1 500 000 coups de pédale.
Le coureur A effectue 2000000 coups de pédale.
Le coureur B effectue 2000000 coups de pédale.
Le coureur A effectue 2 500 000 coups de pédale.
Le coureur B effectue 2 500 000 coups de pédale.
Le coureur A effectue 3000000 coups de pédale.
Le coureur B effectue 3000000 coups de pédale.
Le coureur A effectue 3 500 000 coups de pédale.
Le coureur B effectue 3 500 000 coups de pédale.
Le coureur A effectue 4000000 coups de pédale.
Le coureur B effectue 4000000 coups de pédale.
Le coureur A effectue 4 500 000 coups de pédale.
Introduction aux threads en Java 19

 
Le coureur B effectue 4 500 000 coups de pédale.
Le coureur A effectue 5000000 coups de pédale.
Le coureur B effectue 5000000 coups de pédale.

CCONCLUSION .– Java n'exige pas que le système soit "time-sliced": cela


c'est-à-dire que le même temps est accordé aux threads avec le même niveau de
priorité.

1.8.3. Priorité entre les threads

La méthode setPriority définit un niveau de priorité entre les différents


fils. La valeur doit être comprise entre une valeur minimale, MIN_PRIORITY,
et un maximum, MAX_PRIORITY.

Le code suivant illustre le rôle des priorités entre les threads :

public class InfernalRace2 { public static void main(String[] args) { Runner A = new Runner("A"); Coureur B = no

A.setPriority(Thread.MAX_PRIORITY);
B.setPriority(Thread.MIN_PRIORITY);
System.out.println("Thread Runner " + A.name + " a la priorité = "
+ A.getPriority());
System.out.println("Thread Runner " + B.name + " a la priorité = "
+ B.getPriority()); Un début(); B.start(); }

class Runner extend Thread { String name public Runner(String name) { super(name); this.name = nom; } publ

20 Programmation simultanée, temps réel et distribuée en Java while (PedalStroke < 5000000) { PedalStroke++; if ((Ped
 

PedalStroke + "coups de pédale."); }

}
}
}

Exécution:

Thread Runner A a la priorité = 10


Thread Runner B a la priorité = 1
Le coureur A effectue 500 000 coups de pédale.
Le coureur A effectue 1000000 coups de pédale.
Le coureur A effectue 1 500 000 coups de pédale.
Le coureur A effectue 2000000 coups de pédale.
Le coureur A effectue 2 500 000 coups de pédale.
Le coureur A effectue 3000000 coups de pédale.
Le coureur A effectue 3 500 000 coups de pédale.
Le coureur A effectue 4000000 coups de pédale.
Le coureur A effectue 4 500 000 coups de pédale.
Le coureur A effectue 5000000 coups de pédale.
Le coureur B effectue 500 000 coups de pédale.
Le coureur B effectue 1000000 coups de pédale.
Le coureur B effectue 1 500 000 coups de pédale.
Le coureur B effectue 2000000 coups de pédale.
Le coureur B effectue 2 500 000 coups de pédale.
Le coureur B effectue 3000000 coups de pédale.
Le coureur B effectue 3 500 000 coups de pédale.
Le coureur B effectue 4000000 coups de pédale.
Le coureur B effectue 4 500 000 coups de pédale.
Le coureur B effectue 5000000 coups de pédale.

Introduction aux threads en Java 21

 
Le thread A a accédé au processeur en premier car il avait la priorité maximale.
Le thread B s'exécute à la fin du thread A car il a la priorité minimale.
La priorité nous a permis de définir un ordre d'exécution entre les deux threads.
– Priorité entre les threads : méthode setPriority(int).
– Les priorités des threads sont comprises entre 1 et 10.
– Trois constantes représentent les valeurs limites et médianes.
– Plus la valeur est élevée, plus la priorité du thread pour accéder au
processeur.
– MIN_PRIORITY : priorité minimale (1).
– NORM_PRIORITY : priorité normale (5).
– MAX_PRIORITY : priorité maximale (10).
– La priorité normale est attribuée par défaut à un nouveau thread.

1.9. Programmation d'une tâche : Timer et TimerTask

Dans la section suivante, nous souhaitons programmer des tâches soit après un
retard (c'est le cas pour le lancement d'une simulation après un certain temps, par
exemple) ou après un certain délai mais périodiquement (dans le cas d'un logiciel
mises à niveau).

1.9.1. En spécifiant un délai initial

Le code suivant lance une tâche une fois après 4 secondes.

importer java.util.Scanner ;
importer java.util.TimerTask ;
importer java.util.Timer ;
class InitiateTheParrot11{ public static void main(String args[]) { Parrot11 perroquet = new Parrot11("coco", 3); Minut
22 Programmation simultanée, temps réel et distribuée en Java do { System.out.println("blabla"); System.out.println("b
 

importer java.util.TimerTask ;
la classe Parrot11 étend TimerTask { chaîne privée cri = null; int privé fois = 0 ; public Parrot11(String s, int i) { cri = s;

Exécution:

blabla
blabla
Voulez-vous continuer à discuter ? (o/n)
y
blabla

Introduction aux threads en Java 23

 
blabla
Voulez-vous continuer à discuter ? (o/n)
coco
coco
coco
y
blabla
blabla
Voulez-vous continuer à discuter ? (o/n)
n

La définition de la classe TimerTask est la suivante :


– la classe abstraite publique TimerTask étend l'objet implémente Runnable.
– TimerTask est une classe abstraite qui implémente Runnable, donc un run()
méthode; nous
méthode qui devons
code hériter
la tâche de la classe TimerTask et redéfinir run()
à effectuer.
– run() est la seule méthode abstraite de TimerTask.
– Un Timer démarre l'exécution de la tâche.
– Un Timer correspond à un thread qui exécutera successivement le
Tâches.
– schedule(task, long milliseconds) programme la tâche en spécifiant une
délai initial en millisecondes.
– La méthode cancel() arrête la programmation du Timer.

1.9.2. Avec un délai initial et une périodicité

importer java.util.Scanner ;
importer java.util.TimerTask ;
importer java.util.Timer ;
class InitializeTheParrot11{ public static void main(String args[]) { Parrot11 parrot = new Parrot11("coco", 3); Mi

24 Programmation simultanée, temps réel et distribuée en Java timer.schedule(parrot, 3000, 2000); Chaîne réponse="o
 

importer java.util.TimerTask ;
la classe Parrot11 étend TimerTask { chaîne privée cri = null; int privé fois = 0 ; public Parrot11(String s, int i) { cr

schedule(task, long delay, long period) programme la tâche après un certain délai
pour les exécutions périodiques : les temps sont donnés en millisecondes.
Introduction aux threads en Java 25

 
Exécution:

blabla
blabla
Souhaitez-vous que le perroquet continue ? (o/n)
coco
coco
coco
coco
coco
coco
ncoco coco
coco

La tâche est lancée au bout de 3 secondes pour une exécution périodique de


2 secondes.

Synchronisation des threads

2.1. Synchronisation à la fin : méthode join()


Dans l'exemple suivant, il y a deux rédacteurs (EcrivainA et
EcrivainB) : l'un écrira ABC 10 fois et l'autre écrira XYZ 10
fois. Chaque mot (ABC ou XYZ) sera écrit lettre par lettre avec un retour
après la dernière lettre de chaque mot.
1
Le code suivant représente ce scénario :

 1 Pour une version couleur des codes apparaissant dans ce chapitre, voir www.iste.co.uk/benmammar/
java.zip

28 Programmation simultanée, temps réel et distribuée en Java

 
Exécution:

AXBYC
ABZ
XC
AYZ
XBC
AYBC
AZ
XBC
AYBC
AZ
XBC
ABYZ
XC
AYBC
AZ
XBC
Ecrivain de ABC a fini
YZ
XYZ
XYZ
XYZ
Ecrivain de XYZ a fini

Il y a concordance entre les deux auteurs et c'est la raison pour laquelle nous
obtenu l'exécution ci-dessus. En utilisant la méthode join() de la classe Thread,
nous pouvons synchroniser les deux threads. EcrivainB sera lancé après la
terminaison d'EcrivainA (classe Prog55.java suivante).

Le code présenté après join() sera exécuté après la fin du


exécution du code situé avant join().
Synchronisation des fils 29

 
Ce type de synchronisation fait appel à la terminaison.

public class Prog55 { public static void main (String argv[]) { Ecrivain EcrivainA, EcrivainB; EcrivainA = nouvel Ecriva

Exécution:

abc
abc
abc
abc
abc
abc
abc
abc
abc
abc
Ecrivain de ABC a fini
XYZ
XYZ
XYZ

30 Programmation simultanée, temps réel et distribuée en Java

 
XYZ
XYZ
XYZ
XYZ
XYZ
XYZ
XYZ
Ecrivain de XYZ a fini
2.2. Ressource en exclusion mutuelle : modificateur synchronisé
Prenez le code suivant : la méthode print affiche un mot lettre par lettre
avec un saut de ligne à la fin de la dernière lettre. Les écrivains passent par une imprimante
pour écrire.

Les deux auteurs s'adressent maintenant à une imprimante commune. Ils "écrasent" le
variable texte de l'imprimeur (imprimeur). Le tout est encore illisible (voir
l'exécution ci-dessous).

Synchronisation des fils 31

 
Exécution:

AXYYZ
Z
AXYYZ
Z
AXYYZ
CA
XYYZ
Z
XABBC
C
AXYYZ
Z
AXYYZ
Z
XABBC
C
XABBC
C
XABBC
Ecrivain de XYZ a fini
C
Ecrivain de ABC a fini

L'utilisation de "synchronisé" est indispensable pour résoudre ce problème, tout comme


indiqué dans le code suivant :
32 Programmation simultanée, temps réel et distribuée en Java

« synchronisé » définit un blocage ou une exclusion mutuelle sur la méthode d'impression : à


la plupart des threads peuvent exécuter la méthode à la fois.

Cette nouvelle exécution donne les résultats suivants :

Exécution:

abc
XYZ
abc
XYZ
abc
XYZ
abc
XYZ
abc
XYZ
abc
XYZ

Synchronisation des fils 33

 
abc
XYZ
abc
XYZ
abc
XYZ
abc
XEcrivain de ABC a fini
YZ
Ecrivain de XYZ a fini

Un écrivain, au moment de l'exécution de la méthode, possède le verrou de cette méthode.


Le verrou est attaché à l'itération suivante et donc au deuxième rédacteur
prend le relais pour afficher son texte et ainsi de suite jusqu'à la fin de l'exécution.

2.3. Variables partagées : classe interne

Dans ce qui suit, nous aborderons le partage de variables entre les threads. La
le code suivant illustre un scénario d'utilisation :

34 Programmation simultanée, temps réel et distribuée en Java

 
La classe Perroquet20 est située dans la classe PerroquetsMatheux20, et
la méthode join est utilisée pour ne pas afficher la valeur du compteur avant
la terminaison des deux threads.

Du fait de ces règles de visibilité en Java, la variable compteur


(comptoir) est visible/accessible depuis la classe Perroquet20 et donc depuis
les deux objets threads perroquetA et perroquetB ; cependant, l'exemple
les variables cri et fois de Perroquet20 existent en autant d'exemplaires
comme exemples de Perroquet20. Les deux threads accèdent donc à un partage/commun
espace de variables.

Contrairement aux processus qui possèdent chacun leurs propres


espace de travail, séparé des autres processus.

Exécution:

Coco 1
bonjour 2
bonjour 3
Coco 4
bonjour 5
coco 6
bonjour 7
bonjour 8
bonjour 9
coco 10
bonjour 11
Coco 12
bonjour 13
Coco 14
Coco 15
bonjour 16
Coco 17
bonjour 18
Coco 19
coco 20
compteur = 21

Synchronisation des fils 35

 
2.4. Le problème des exclusions mutuelles

Pour illustrer le problème des exclusions mutuelles, nous remplacerons le


instruction counter++, localisée dans la classe Perroquet20 par son équivalent :
valeur = compteur + 1 et compteur = valeur (valeur).

Les deux fils de perroquet (perroquet) fonctionnent en alternance : un fil peut être


en pause au milieu de l'exécution de sa méthode de répéteur (repeter) afin que le
contrôleur de thread permet à l'autre de s'exécuter (dormir entre les deux nouveaux
instructions dans la classe Perroquet21).

36 Programmation simultanée, temps réel et distribuée en Java

 
Exécution:

Coco 1
bonjour 1
Coco 2
Coco 3
bonjour 3
Coco 4
bonjour 4
coco 5
bonjour 5
coco 6
bonjour 7
Coco 7
bonjour 8
Coco 8
bonjour 9
Coco 9
coco 10
bonjour 10
bonjour 11
bonjour 12
compteur = 12

La variable compteur est partagée, mais la valeur est différente pour chaque thread. Ce
arrivera que les deux threads doivent travailler sur la même valeur avant de pouvoir
pour l'incrémenter.

2.5. Bloc synchronisé

Dans ce qui suit, la section où pointe la flèche est celle qui provoque un
publier:

Synchronisation des fils 37

Il faut donc trouver le moyen de définir cette partie comme une section critique. La
Voici la solution à ce problème :
38 Programmation simultanée, temps réel et distribuée en Java

 
– Nous avons créé une nouvelle classe Counter afin de créer un nouveau « compteur »
objet car la syntaxe de "synchronisé" l'exige.
– Le mot-clé "synchronisé" définit un bloc d'instructions qui peut
ne s'exécutent qu'en exclusivité, même si d'autres threads souhaitent l'exécuter.
– Lorsque le thread perroquetA exécute ce bloc synchronisé et que le
le thread perroquetB souhaite commencer l'exécution de ce même bloc, perroquetB
doit attendre. Une fois perroquetA terminé, perroquetB peut prendre le relais.
– Un seul thread peut exécuter le bloc synchronisé à la fois.
– Le bloc est dit en exclusion mutuelle voire critique
section.
– Si les autres threads souhaitent exécuter cette section, ils doivent attendre le
fil de section critique pour finir.
– S'il y a plus d'un thread en attente d'un bloc synchronisé
lorsqu'il sera disponible, le contrôleur n'autorisera qu'un seul à s'exécuter.
– L'appel sleep() n'entraîne pas la sortie d'un thread de la section critique.

Exécution:

bonjour 1
Coco 2
bonjour 3
Coco 4
bonjour 5
coco 6
Coco 7
bonjour 8
Coco 9
bonjour 10
bonjour 11
Coco 12
Coco 13
bonjour 14

Synchronisation des fils 39

 
Coco 15
bonjour 16
bonjour 17
Coco 18
Coco 19
bonjour 20
compteur = 20

Le schéma suivant illustre trois threads qui sont en commun


exclusion par rapport à un seul objet.

Le troisième thread exécutera la section critique si elle est libérée par le


fil central.

Graphique 2.1. Section critique partagée entre trois threads. Pour un


version couleur de la figure, voir www.iste.co.uk/benmammar/java.zip

40 Programmation simultanée, temps réel et distribuée en Java

Illustration 2.2. Section critique partagée entre trois threads. Pour une couleur
version de la figure, voir www.iste.co.uk/benmammar/java.zip

synchronized(object) signifie que le bloc est en exclusion mutuelle par rapport à


un moniteur de cet objet (objet) : les threads qui se synchronisent autour de
même objet sont en exclusion mutuelle.

Les fils de gauche et du centre ont une section critique, le fil de droite


a une section critique, mais pas avec les deux autres fils.

Le moniteur "joue" le rôle de superviseur, s'assurant qu'un seul thread


à la fois peut exécuter la section critique surveillée par lui : c'est un verrouillage
mécanisme.

Synchronisation des fils 41

 
Le mécanisme de "moniteur" d'un objet s'applique à toutes les instances d'Objet :
il s'agit donc d'un mécanisme implémenté au cœur de Java.

Le thread qui exécute la "synchronisation" d'un objet en devient le propriétaire


du moniteur de cet objet.

Thread.sleep ne supprime pas la propriété d'un moniteur, pas même


temporairement.

2.6. Méthode d'instance synchronisée

Dans ce qui suit, nous allons modifier le code de la section 2.5 afin de
créer une méthode d'instance synchronisée. Le nouveau code est le suivant :

42 Programmation simultanée, temps réel et distribuée en Java


  L'exécution résultante est illustrée dans le diagramme ci-dessous.

Plus1 est une méthode qui utilise l'exclusion mutuelle et incrémente "val" d'un
unité pour chaque thread qui possède le verrou de la méthode.

– En Java, chaque objet possède un seul moniteur (superviseur) qui peut


conserver les sections critiques. Il garantit qu'un seul thread peut exécuter le processus critique
section supervisée par le moniteur,
– moniteur synchronisé (objet) { instr } associé à l'objet.
– moniteur Synchronized void Method(){....} associé à la méthode.
– L'objet est verrouillé pendant l'exécution du bloc synchronisé.
– En Java, chaque objet possède également une file d'attente.

Synchronisation des fils 43

2.7. Variables partagées : variable de classe

Dans la section suivante, nous présenterons un exemple de fil


coopération (enseignant contre élèves). Nous avons un enseignant qui enseigne aux nouveaux étudiants
mots.

Les élèves doivent répéter chaque mot trois fois, peut-être plus.
Illustration 2.3. Exemple consommateur/producteur. Pour une version couleur
de la figure, voir www.iste.co.uk/benmammar/java.zip

44 Programmation simultanée, temps réel et distribuée en Java

 
Le code naïf de cet exemple est le suivant :

Exécution:

nouveau mot pour perroquet ? (sans non)


bla
nouveau mot pour perroquet? (sans non)
coco bla
coco bla
coco bla
jaco bla
jaco bla
jaco bla
bleu
nouveau mot pour perroquet ? (sans non)
coco bleu
coco bleu
coco bleu
Synchronisation des fils 45

 
bof
coco bleu
coco bleu
coco bleu
coco bleu
coco bleu
coco bleu
nouveau mot pour perroquet ? (sans non)
jacoblo
jacoblo
jacoblo
jacoblo
jacoblo
.....

Une variable statique "mot" (appelée variable de classe) est partagée entre
les deux fils de perroquet qui doivent l'apprendre puis le répéter. Par la suite, le
thread principal entre un autre mot (mot).

Au début, ils doivent attendre avec une boucle le premier mot et si le professeur
fournit trop vite de nouveaux mots, les deux perroquets risquent d'en "sauter" !

Objectif : éviter un mécanisme d'attente entre le professeur et le


élèves perroquets : les élèves attendent un nouveau mot (nouveau mot) pour apprendre, le
le professeur, lorsqu'il enseigne un mot, doit attendre que les élèves aient eu le temps
pour l'apprendre et le répéter, avant d'en enseigner un nouveau.

2.8. Synchronisation entre les threads

2.8.1. Attendez et notifiez tout

Dans le code suivant, nous avons créé un objet appelé attheboard pour partager
entre producteur et consommateur. Cet objet doit avoir deux méthodes : la

46 Programmation simultanée, temps réel et distribuée en Java

 
le premier est utilisé par le consommateur (learn) et le second est utilisé par le
producteur (enseigner).
Exécution:

nouveau mot pour perroquet ? (sans non)


Maître
nouveau mot pour perroquet ? (sans non)
maître coco
maître coco
maître coco
maître jaco

Synchronisation des fils 47

 
maître jaco
maître jaco
DSR
coco RSD
coco RSD
coco RSD
Jaco RSD
Jaco RSD
Jaco RSD
nouveau mot pour perroquet ? (sans non)
non

– Les méthodes wait et notifyAll sont définies dans la classe


java.lang.Object et sont donc hérités par toute la classe (y compris
notifier).
– La méthode wait met en pause (bloque) l'exécution d'un thread, jusqu'à ce qu'une condition
est rencontré. Le respect de cette condition est signalé par un autre thread à l'aide de notify ou
méthodes notifyAll.
– Lorsque la méthode d'attente est invoquée à l'aide d'une méthode synchronisée,
en même temps que l'exécution est mise en pause, le verrou posé sur l'objet par
laquelle la méthode a été invoquée est abandonnée. Dès que la condition de réveil
survient, le thread attend de pouvoir reprendre le verrou et poursuivre la
exécution.
– Notez ici la principale différence entre dormir et attendre :
- sleep bloque l'exécution mais le thread maintient le verrou
(met fin à son exécution);
- wait bloque l'exécution mais le thread libère le verrou (une seconde
thread peut exécuter la section critique).
48 Programmation simultanée, temps réel et distribuée en Java

 
2.8.2. Attendez et notifiez

Dans le code suivant, par rapport à la section précédente, nous avons


a remplacé notifier tout par notifier :

Exécution:

coco bonjour
coco bonjour
coco bonjour

Synchronisation des fils 49

 
DSR
coco RSD
coco RSD
coco RSD
nouveau mot pour perroquet ? (sans non)
bis
jaco encore
jaco encore
jaco encore
nouveau mot pour perroquet ? (sans non)
bizarre
coco bizarre
coco bizarre
coco bizarre
nouveau mot pour perroquet ? (sans non)
vraiment
jaco vraiment
jaco vraiment
jaco vraiment
nouveau mot pour perroquet ? (sans non)
non

Un seul thread est libéré de l'attente par la notification. Un seul perroquet


l'élève apprend à la fois.

notify() ne réveille qu'un seul thread à la fois ; il n'y a pas de spécification sur
le fil sélectionné ! C'est le contrôleur de thread qui choisit (le mécanisme est
pas sur une base FIFO).

Un deuxième exemple de producteur-consommateur est illustré ci-dessous.


chiffre. Il propose aux usagers arrivant en gare d'attendre un bus ; un bus arrive,
charge les utilisateurs dans la station et s'en va. Si un utilisateur arrive trop tard, il
rater le bus.

50 Programmation simultanée, temps réel et distribuée en Java

class BusSimple { public static void main (String args[]) { Station BusStation = new Station (); Bus b = nouveau Bus (

BusStation, 3000), nouvel utilisateur("C",BusStation, 1000), nouvel utilisateur ("D",BusStation,

1500), new Usager ("E",BusStation, 1000)} ; b.start () ; for (int i = 0 ; i < u.length ; i++) u[ i ].start () ; }

L'objet partagé est BusStation (de type Station) ; il contient deux méthodes
comme prévu. L'un est utilisé par le producteur (chargerUsagers) et l'autre est
utilisé par le consommateur (attendreBus).
Synchronisation des fils 51

 
La classe BusSimple est utilisée pour lancer cinq utilisateurs ainsi que le bus.

Exécution:

C arriver à la gare
E arriver à la gare
A arriver à la gare
D arriver à la gare
Le bus arrive à la gare
D est monté dans le bus
A est monté dans le bus
E est monte dans le bus
C'est monté dans le bus
Bus départ de la gare
B arriver à la gare

2.9. Modèle classique producteur-consommateur

On parlera de schémas classiques dans le cas d'un seul producteur et d'un


seul consommateur.

L'exemple utilisé ici est celui d'un professeur enseignant à un étudiant quatre nouvelles
mots. L'élève doit répéter chaque mot trois fois.

Illustration 2.4. Modèle classique producteur-consommateur. Pour une couleur


version de la figure, voir www.iste.co.uk/benmammar/java.zip

52 Programmation simultanée, temps réel et distribuée en Java

 
Le code de cet exemple est le suivant :
Exécution:

nouveau mot a enseigner au perroquet?


Parler
nouveau mot a enseigner au perroquet?
Coco Parle
Coco Parle
Coco Parle
Loger
nouveau mot a enseigner au perroquet?
coco maison
coco maison
coco maison
Bonjour

Synchronisation des fils 53

 
nouveau mot a enseigner au perroquet?
Coco Bonjour
Coco Bonjour
Coco Bonjour
Discuter
Coco Chat
Coco Chat
Coco Chat

– Le problème est simplifié, un seul maître et un seul perroquet, et


l'un attend l'autre :
- Le perroquet attend un nouveau mot pour apprendre.
- Le professeur attend que le perroquet lise et répète le mot.
– Ce schéma classique est connu sous le nom de Producteur-Consommateur :
– Les deux fonctions doivent être synchronisées afin que :
- le consommateur attend qu'il n'y ait plus rien à consommer.
- tant que (motAuTableau == null)
- le producteur attend alors que le consommateur n'est pas prêt à consommer.
- tandis que (motAuTableau != null)
54 Programmation simultanée, temps réel et distribuée en Java

 
Il peut aussi y avoir des modèles avec plus d'un producteur et plus d'un
consommateur. Un tel exemple est le suivant :
– Les threads « producteurs » qui produisent des données et les placent dans un message
file d'attente.
– Les threads «consommateurs» qui récupèrent les données de la file d'attente.
– Les producteurs et les consommateurs ne doivent pas accéder à la file d'attente en même temps
temps (section critique).

Une implémentation possible de ce modèle réaliserait la synchronisation


par rapport à la file d'attente des messages.

- Les producteurs:
- lorsque la file d'attente est pleine : bloquer ;
- lorsqu'il reste des places disponibles : débloquer.
– Les consommateurs :
- lorsque la file d'attente est vide : bloquer ;
- lorsqu'il y a des données dans la file d'attente : débloquer.

2.10. Sémaphore en Java

réEFINITION .– Un sémaphore est un objet de haut niveau qui permet de gérer


accès simultané à une ressource partagée, qui accepte au plus N
accéder.

Synchronisation des fils 55

 
Exemples:
– gérer les entrées/sorties d'un parking de N places ;
– gestion d'une salle d'attente dans un cabinet médical, un salon de coiffure,
compagnie d'assurance, etc. avec N chaises disponibles.

2.10.1. Avant Java 1.5

Un objet sémaphore encapsule un entier et deux opérations atomiques de


incrémentation et décrémentation. La méthode P contient l'attente et
la méthode V contient la notification.
Une classe possible utilisée pour implémenter le sémaphore est la suivante :

Chaque thread qui souhaite accéder à la section critique doit utiliser la méthode
P et, par la suite, il décrémentera la variable du compteur.

Après accès par les quatre premiers threads, le contenu de la section critique
et la valeur du compteur sera la suivante dans le cas où un cinquième
thread devrait souhaiter accéder à la section critique.

56 Programmation simultanée, temps réel et distribuée en Java

Si T4 sort de la section critique, il doit utiliser la méthode V (incrémenter le


compteur et faire une notification).
Comme T5 est le seul thread à avoir été bloqué par wait, il entrera dans le
section critique après avoir reçu une notification de T4.

Synchronisation des fils 57

Une fois que tous les threads sont sortis de la section critique, l'état de cette dernière
et la valeur du compteur sera la suivante :

2.10.2. Après Java 1.5

La version 1.5 de Java et son package java.util.concurrent (avec tous


ses sous-packages) fournissent des outils de synchronisation de haut niveau.

– Opération P (acquire()) : décrémente le compteur ; bloque s'il est négatif


en attendant de pouvoir le décrémenter.
– Opération V (release()) : incrémente le compteur ; envoie une notification si le
compteur est négatif ou nul.

58 Programmation simultanée, temps réel et distribuée en Java

 
– On peut voir le sémaphore comme un ensemble de puces, avec deux opérations :
- prendre une puce ou, si nécessaire, attendre qu'il y en ait une de disponible ;
- acquiert(), généralement avec attente ;
- remettre une puce ;
- release(), généralement avec notification.
– Un sémaphore a une puce et il est très similaire à une serrure.

L'exemple suivant est la gestion d'un parking à l'aide de


sémaphores. Le paramètre de classe Sémaphore indique la taille du lot.

Exécution:

Le fil 2 n'est pas dans le parking


Le fil 3 n'est pas dans le parking
Le fil 4 n'est pas dans le parking
Le fil 0 n'est pas dans le parking

Synchronisation des fils 59

 
Le fil 1 n'est pas dans le parking
Le fil 1 entre dans le parking
Le fil 2 entre dans le parking
Le fil 3 entre dans le parking
Le fil 1 sort du parking
Le fil 0 entre dans le parking
Le fil 0 sort du parking
Le fil 4 entre dans le parking
Le fil 2 sort du parking
Le fil 4 sort du parking
Le fil 3 sort du parking

En remplaçant l'instruction Semaphore sem = new Semaphore(3) par


Semaphore sem = new Semaphore(1) dans le code précédent, on obtient le
exécution suivante :

Le fil 2 n'est pas dans le parking


Le fil 3 n'est pas dans le parking
Le fil 4 n'est pas dans le parking
Le fil 0 n'est pas dans le parking
Le fil 1 n'est pas dans le parking
Le fil 0 entre dans le parking
Le fil 0 sort du parking
Le fil 4 entre dans le parking
Le fil 4 sort du parking
Le fil 1 entre dans le parking
Le fil 1 sort du parking
Le fil 3 entre dans le parking
Le fil 3 sort du parking
Le fil 2 entre dans le parking
Le fil 2 sort du parking
3

Systèmes temps réel et Java temps réel

3.1. Systèmes en temps réel

3.1.1. Définition

Les systèmes en temps réel se distinguent des autres systèmes par le fait
qu'ils tiennent compte des contraintes temporelles dans les cas où
le respect des délais est aussi important que la précision du résultat ; en d'autre
mots, le système ne doit pas seulement fournir des résultats exacts, il doit les fournir
dans les délais impartis.

En calcul temps réel, le bon comportement d'un système dépend non seulement
sur les résultats du traitement logique, mais aussi sur le moment où les résultats sont
produit [STA 88].

Exemples:

– Un guichet automatique ne peut pas prendre 5 minutes pour livrer de l'argent.


– Un radar ne peut pas mettre 2 secondes pour réagir.
– Un système ABS ne peut pas prendre plus de 150 ms pour acquérir des informations
et 1 seconde pour réagir (ABS fait référence aux systèmes de freinage assisté utilisant dans le moteur
véhicules, qui évitent le blocage des roues en cas de freinage intense).

62 Programmation simultanée, temps réel et distribuée en Java

 
3.1.2. Exemples de systèmes d'exploitation en temps réel

– Windows CE (Windows Embedded Compact), souvent appelé


WinCE, est une déclinaison de Windows pour les systèmes embarqués et autres minimalistes
systèmes utilisés, par exemple, dans les ordinateurs de poche.
– LynxOS est un système d'exploitation en temps réel basé sur la plate-forme UNIX
développé par la société LynuxWorks pour les applications embarquées.
– LynxOS est principalement utilisé pour les systèmes embarqués tels que l'aviation critique
logiciel, le militaire, la fabrication industrielle et dans la communication
industrie.

3.1.3. Types de temps réel

– Temps réel flexible :

- La réponse du système après un certain délai diminue progressivement


Son usage.
- Les pénalités ne sont pas catastrophiques.
- Ces systèmes acceptent des variations de traitement de données de l'ordre d'un
demi-seconde (ou 500 ms) ou même une seconde.
Exemples : systèmes multimédias, si quelques images ne s'affichent pas,
il ne compromet pas le fonctionnement de l'ensemble du système.

– Temps réel ferme :

- Une réponse du système dans des délais acceptables est essentielle.


- Le résultat n'est d'aucune utilité après la date limite.
Exemples : transactions financières, etc.

– Temps réel dur :


- La réponse du système dans les délais est vitale.
- Une absence de réponse est catastrophique.
Exemples : contrôle du trafic aérien, contrôle des réacteurs nucléaires, etc.

Systèmes temps réel et Java temps réel 63

 
3.1.4. Architecture

– Architecture monotâche
- Un système temps réel avec une seule tâche est facile à définir. Tout ce qu'il faut, c'est
répéter indéfiniment la liste de tâches suivante :
- Attendez le stimulus.
- Agir en fonction du stimulus.
Exemple : radar automatique.

– Architecture multitâche :
- Chaque tâche peut être effectuée par un thread. Une ressource peut être
protégé par une section critique. La longueur des sections critiques affecte la
temps de réponse du système.
Exemple : système de détection d'intrusion (IDS) qui effectue diverses tâches dans
en temps réel, y compris :
– Analyse du trafic Internet ;
– identifier les adresses IP impliquées dans une attaque ;
– reconfiguration du pare-feu ;
– notification visuelle de l'alerte : affichage de l'alerte dans une ou plusieurs
consoles de gestion.

3.1.5. Ordonnance de tâche avec priorités

Les priorités aident à organiser les tâches. Plus la valeur d'une priorité est élevée, plus
plus la priorité d'accès d'une tâche au processeur est élevée. Les priorités peuvent être
dynamique ou statique.
Dans un système temps réel, une tâche n'est généralement jamais bloquée par un
tâche prioritaire.
– Considérons deux threads T1 et T2.
– T1 a une haute priorité (ex : 10), T2 a une basse priorité (ex : 1).

64 Programmation simultanée, temps réel et distribuée en Java

 
– Au lancement des deux threads, T1 doit s'exécuter avant T2.
– A un certain moment, T1 est bloqué (ex : par wait()), T2 s'exécute et entre dans le
section critique.
– T1 débloque et tente d'acquérir le verrou.
– T2 n'est jamais ordonné et ne libère jamais la serrure
– T2 s'exécute avant T1.
– La non gestion de l'inversion de priorité peut avoir des effets désastreux.
– En effet, comme l'absence de gestion de l'inversion de priorité implique que
une tâche hautement prioritaire ne peut pas s'exécuter, il est possible de réagir en cas d'urgence
situations à ne pas réaliser.

Exemple : ordre d'arrêt d'urgence d'un réacteur nucléaire qui serait


bloqué par un ordre de priorité mineure.
– Il n'existe pas de solution simple qui évite les inversions de priorité. Il est,
néanmoins possible de mettre en place des mesures limitant ces risques. Dans
notamment, il est possible de n'autoriser l'accès aux sections critiques qu'aux
threads de priorité similaire.
Deux solutions sont proposées :
– Plafond prioritaire :
- Priority Ceiling Protocol (PCP) : il consiste à associer une priorité
plafond de chaque ressource. La priorité de la ressource parmi les tâches pouvant utiliser
il plus 1. L'ordonnance transfère cette priorité à chaque tâche qui y accède
Ressource. Lorsqu'une tâche a fini d'utiliser la ressource, sa priorité revient à
sa valeur d'origine.
– Héritage prioritaire :
- Priority Inheritance Protocol (PIP) : lorsqu'une tâche T2 (haute priorité) est
bloqué lors de l'accès à la ressource R, la tâche bloquante T1 (basse priorité)
hérite de la priorité de T2. T1 utilise la priorité héritée jusqu'à la libération
de R, où il retrouve la priorité qu'il avait avant son acquisition.

Systèmes temps réel et Java temps réel 65

 
3.2. Java en temps réel

À l'origine, Java a été conçu pour les appareils embarqués. Mais Java rapidement
commencé à être utilisé pour les applications Web (applets). Java a ensuite été utilisé pour la
candidatures suivantes :
– Classic J2SE (Java 2 Standard Edition) pour le poste client (JVM
classique, Point d'accès).
– Business J2EE (Java 2 Enterprise Edition) pour le développement de
applications métier (HotSpot).
– J2ME réduit (Java 2 Micro Edition) pour les systèmes mobiles tels que
tablettes d'assistants personnels et téléphones portables (JVM classique, Kilo VM (KVM),
Carte VM (JVM embarquée sur puce imprimée)).

Notez que le langage ici n'est pas conçu pour le temps réel, pour le
les raisons suivantes:

– manque d'expertise en matière d'ordonnancement ;


– Windows a du mal à gérer les priorités entre les threads ;
– pour notify() ou notifyAll() il n'y a pas de spécifications sur le sélectionné
thread, pas nécessairement FIFO ;
– c'est une des rares fonctionnalités Java qui n'est pas mobile ;
– problèmes avec Garbage Collector/GC ;
– GC peut être exécuté à tout moment ;
– GC peut durer un certain temps ;
– GC ne peut pas être préempté ;
– GC préempte tout thread.

3.2.1. RTSJ (spécification temps réel pour Java)

Il s'agit d'une proposition d'extension de la JVM avec des fonctionnalités temps réel
(JVM RTSJ) [JAV 17a].

66 Programmation simultanée, temps réel et distribuée en Java

 
Cahier des charges réalisé par un groupe d'experts de plusieurs cabinets
(Sun, IBM, QNX, Nortel, etc.).
Restrictions :

– Compatibilité avec l'existant : toute application Java non temps réel peut s'exécuter
sur une JVM RTSJ.
– Pas d'extension syntaxique.
– Aucun prérequis matériel ou électrique.
– Temps d'exécution prévisible.
– Granularité : nanosecondes. 64 bits (ms) + 32 bits (ns).
– RTSJ améliore la programmation Java temps réel dans les domaines suivants :
- Gestion de la mémoire.
– Horloge et gestion du temps.
– Ordonnance et objets « planifiables ».
– Fils en temps réel.
– Gestion des événements asynchrones et des temporisateurs.
– Synchronisation et partage des ressources.
– Paquet ajouté : javax.realtime [JAV 17b].
– Trois interfaces.
– 47 cours.
– 15 dérogations.
– Quatre erreurs.
– RTSJ : Fils
– Une interface :
– Programmable : objet qui peut être ordonné, s'étend
java.lang.Runnable.
– l'interface publique Schedulable étend java.lang.Runnable.
– Deux nouvelles classes :

Systèmes temps réel et Java temps réel 67

 
– Fil en temps réel.
– la classe publique RealtimeThread étend les implémentations de java.lang.Thread
Programmable.
– Peut accéder au tas. De moindre priorité que le GC (DestroyJavaVM).
– NoHeapRealtimeThread.
– la classe publique NoHeapRealtimeThread étend RealtimeThread.
– Sa priorité est supérieure au GC. Fonctionne dans la mémoire étendue,
les objets de ce thread sont alloués à cette mémoire.
– ScopedMemory présente les caractéristiques suivantes :
– A la durée de vie du thread temps réel qui l'occupe.
– N'est pas géré par le ramasse-miettes.
– Les objets peuvent être alloués dans une ScopedMemory (plutôt que dans
HeapMemory).
– Les objets sont libres à la fin de la portée.
– Un NoHeapRealtimeThread ne peut pas accéder au Heap.
– Par conséquent, un NoHeapRealtimeThread ne peut pas être bloqué par le Garbage
Collectionneur.
– Les priorités sont organisées comme suit :
– Thread < RealtimeThread < Garbage Collector < NoHeapRealtime
Fil de discussion.

3.2.2. Implémentations

Les trois implémentations étudiées suivantes sont :


– Implémentation de référence : RI.
- Destiné à la recherche et visant à démontrer la faisabilité du
Aventure Java en temps réel. Réalisé par la société TimeSys [TIM 17]. Ce
existe aussi pour Linux/x86.

68 Programmation simultanée, temps réel et distribuée en Java

 
1
L'exécution de son code est présentée dans le schéma suivant :
Illustration 3.1. Implémentation de référence

– Implémentation OpenSource : jRate.

jRate est un projet universitaire (Irvine University en Californie) dirigé par Angelo
Corsaro [UCI 17].

jRate est composé de :

– Java.
– C++ (via CNI).
– CNI : Interface native Cygnus
– Même objectif que JNI (Java Native Interface) : permettre l'accès au
JVM via un langage autre que Java.
– CNI : uniquement C++.
– Correspondance directe entre Java et C++.

 1 Pour une version couleur des codes apparaissant dans ce chapitre, voir www.iste.co.uk/benmammar/
java.zip

Systèmes temps réel et Java temps réel 69

 
– Plus simple.
- Plus rapide.
– jRate est une extension de GCJ.

Rappel : le projet GNU a été lancé par Richard Stallman en 1984,


alors qu'il travaillait au MIT (Massachusetts Institute of Technology) dans le
laboratoire d'intelligence artificielle, afin de créer une plateforme gratuite et complète
système opérateur. GNU Compiler Collection, abrégé en GCC, est un ensemble de
compilateurs créés par le projet GNU. GCC est un logiciel libre capable de
compiler divers langages dont C, C++, Objective-C, Java (GCJ),
Ada et Fortran.

– Implémentation propriétaire : JamaicaVM.

Il s'agit d'une solution commercialisée par la société AICAS [AIC 17].


AICAS est un fournisseur majeur de Java Virtual Machine, JamaicaVM, pour
l'électronique embarquée nécessitant du Hard Real-time. Aicas est présent dans l'aéronautique,
segments de marché de l'automobile et de la robotique industrielle. Il possède un temps réel
CG.

Le schéma suivant donne une idée de sa mise en œuvre :


Illustration 3.2. Implémentation de JamaicaVM

Programmation distribuée en Java

4.1. Définition d'une application distribuée

Une application est divisée en plusieurs unités.


– Chaque unité est placée sur une machine différente.
– Chaque unité peut s'exécuter sur un système différent.
– Chaque unité peut être programmée dans une langue différente.

Illustration 4.1. Application monolithique versus application distribuée. Pour une version couleur
de la figure, voir www.iste.co.uk/benmammar/java.zip

72 Programmation simultanée, temps réel et distribuée en Java


 
4.2. Communication dans une application distribuée

4.2.1. Communication bas niveau : socket

4.2.1.1. Définition
Un socket réseau est un modèle qui permet aux processus de communiquer et
synchroniser les uns avec les autres, soit sur la même machine, soit sur un réseau.

Les sockets ont été développés par l'Université de Berkeley (Californie) en 1986.

L'interface originale conçue par Berkeley était pour C mais depuis lors
Les sockets ont été implémentés dans différentes langues.

Pour Java, la classe Socket du package java.net apparaissant dans J2SE 1.4
(6 février 2002) est utilisé pour implémenter les sockets.

4.2.1.2. Modes de communication


Les sockets utilisent directement les services de la couche transport dans le modèle OSI
(protocoles UDP ou TCP), qui utilisent à leur tour les services de la couche réseau
(protocole IP).

Les sockets permettent à deux processus de communiquer entre eux via un


lien identifié par une adresse IP et un numéro de port.
– Mode connecté (comparable à une communication téléphonique), en utilisant le
Protocole TCP. Dans ce mode de communication, une connexion durable est
établi entre les deux processus, de telle sorte que la destination
l'adresse n'est pas requise pour chaque paquet de données.
– Mode non connecté (semblable à une communication par mail), en utilisant le
Protocole UDP. Ce mode nécessite l'adresse de la destination pour chaque
colis et ne nécessite pas d'accusé de réception.
La communication nécessite deux prises : une pour chacun des deux programmes
communiquant sur le réseau.
– Une prise pour le client.
– Une prise pour le serveur (il y a une deuxième prise pour la connexion sur
côté serveur).

Programmation distribuée en Java 73

 
Le mode de communication IP utilisé, UDP ou TCP, correspond au
type de prise.

Dans la section suivante, nous nous concentrerons sur les sockets TCP.

En Java, chaque instance de la classe Socket est associée à un objet de


la classe InputStream (pour lire le socket) et un objet de la
Classe OutputStream (pour écrire le socket).
– Le serveur utilisera deux types de sockets.
– La première, appelée socket de connexion, sert à attendre un client.
– En Java, la création d'un socket de connexion peut se faire par simple instanciation
l'objet de la classe ServerSocket du package java.net.

Exemple : ServerSocket conn = new ServerSocket(10080).


– La seconde, appelée prise de communication, permet de dialoguer avec
le client.
– Une fois la prise de connexion créée, nous lui demanderons alors d'attendre un
client et obtenir la prise de communication qui lui permettra de dialoguer
avec le client.
– Socket comm = conn.accept().
– Nous pouvons ensuite communiquer avec le client en utilisant l'entrée et la sortie
flux associés au socket.
- ServerSocket (numberPort) : crée un objet ServerSocket sur ce port
Numéro.
- accept() : attend une connexion depuis une machine cliente lente. Sur le
l'arrivée d'une demande de connexion d'une machine cliente, une socket est créée pour
connecter au serveur. Ce sera l'objet retourné avec le blocage
méthode accepter().
- ServerSocket (numberPort, int backlog) : crée un objet ServerSocket
sur ce numéro de port avec une file d'attente pour la connexion d'une taille spécifiée par le
arriéré.
– Les demandes de connexion, lorsque la file d'attente est pleine, sont rejetées et provoquent une
exception du côté du client. Par défaut, la taille est de 50.

74 Programmation simultanée, temps réel et distribuée en Java

 
Quelques méthodes :

– accept() : attend une connexion depuis une machine cliente.


– close() : ferme le ServerSocket et toutes les sockets en cours obtenues par
sa méthode d'acceptation.
– isClosed() : indique si le socket est fermé.
– getLocalPort() : renvoie le numéro de port local.
– InetAddress getInetAddress() : renvoie l'adresse du serveur. InetAddress
représente une adresse IP et éventuellement le nom de la machine.
– getHostName() : renvoie le nom d'hôte mémorisé dans l'InetAddress
objet de type, sous la forme d'une chaîne de caractères.
– getHostAddress() : renvoie l'adresse IP mémorisée dans l'InetAddress
objet de type, sous la forme d'une chaîne de caractères.
– getByName (String host) : retourne l'IP (objet InetAddress)
en fonction du nom dans les paramètres.

Le code suivant a pour but de trouver l'adresse IP correspondant à un


nom de la machine passé en argument :

importer java.net.* ;
public class ResolveName{ public static void main(String[ ] args){

InetAddress adresse ;
essayer{
adresse=InetAddress.getByName("localhost");
System.out.println(adresse.getHostAddress());
adresse=InetAddress.getByName("www.facebook.com");
System.out.println(adresse.getHostAddress());
System.out.println(adresse);
}catch(UnknownHostException e) { }
}
}
Programmation distribuée en Java 75

 
Exécution:

127.0.0.1
31.13.75.36
www.facebook.com/31.13.75.36

Contrairement au serveur, le client n'utilise qu'un seul socket : le


prise de communication.
– Connexion au serveur et obtention de la prise de communication.
– Socket comm = nouveau Socket ("localhost", 10080).
– Le constructeur Socket est une méthode de blocage côté client.
Nous pouvons alors communiquer avec le serveur en utilisant l'entrée et la sortie
flux associés au socket.

– Neuf constructeurs dont deux sont dits obsolètes, donc sept


constructeurs, dont :
- Socket (hôte de chaîne, port int).
- Socket (adresse InetAddress, port int).
- Utilise l'IP : InetAdress.
Quelques méthodes :
– close() : ferme correctement le socket et est susceptible de lever une exception
Exception d'hôte inconnue.
– getOutputStream() : retourne un OutputStream pour le socket.
– getInputStream() : retourne un InputStream pour le socket.
– getPort() : renvoie le numéro de port distant sur lequel la socket est
lié.
– InetAddress getInetAddress() : retourne l'adresse du distant
machine.
– isClosed() : indique si le socket est fermé.
– isConnected() : renvoie l'état de la connexion socket.
– connect (SocketAddress adresseSocket) : connecte le serveur au
prise.

76 Programmation simultanée, temps réel et distribuée en Java

 
– connect (SocketAddress adresseSocket, int timeout) : connecte le
socket au serveur avec une valeur d'expiration de l'ordre des millisecondes.
– Lève une SocketTimeoutException si le délai a déjà expiré.
– SocketAddress est une classe abstraite.
– InetSocketAddress hérite de SocketAddress et est une classe représentant
une InetAddress et un port.
– InetSocketAddress(InetAddress ina, int port).
– InetSocketAddress(chaîne hostName, int port).
– getAddress().
– getHostname().
– getPort().

Le code suivant est utilisé pour créer un socket avec un délai d'attente en utilisant
relier:

Socket chaussette = null ;


essayez { int port = XYZ; int timeOut = 3000 ; SocketAddress sockAddr = new InetSocketAddress("host", port); chaussett

lien. sock.connect(sockAddr, timeOut); // Le délai est en millisecondes donc 3

secondes.
} catch (UnknownHostException e) {
} catch (SocketTimeoutException e) {
} capture (IOException e) {
}

Une demande de connexion bloque jusqu'à ce que la connexion soit établie ou que le
le délai expire ; si le délai a expiré, une SocketTimeoutException est
soulevé.

Programmation distribuée en Java 77

 
4.2.1.3. Exemple de communication
Dans ce qui suit, nous présentons un exemple très simple de communication
avec les sockets TCP :
– Le client envoie un message au serveur.
– Le serveur accuse réception et demande le mot suivant.
– Pour se déconnecter, le client envoie le mot « non ».

Illustration 4.2. Exemple de communication avec Sockets. Pour un


version couleur de la figure, voir www.iste.co.uk/benmammar/java.zip

Le code serveur est le suivant :

importer java.net.* ;
importer java.io.* ;
class ServerEcho { public static void main(String args[]) { ServerSocket server = null; essayez { serveur = nouveau Serv
78 Programmation simultanée, temps réel et distribuée en Java while ((recu = sockIn.readLine()) != null) { System.out.
 

essayez {server.close();}
capture (IOException e2) {}
} // fin de la première capture
}// fin main } // fin classe

Figure 4.3. Schéma de communication avec Sockets. Pour une couleur


version de la figure, voir www.iste.co.uk/benmammar/java.zip

Programmation distribuée en Java 79

 
Le code client est le suivant :

importer java.io.* ;
importer java.net.* ;
importer java.util.Scanner ;
public class ClientEcho { public static void main(String[] args) throws IOException { Socket sock = null; PrintWriter soc

Flux())); } catch (UnknownHostException e) { System.err.println("host unreachable: localhost"); System.exit(1); } catch


}

80 Programmation simultanée, temps réel et distribuée en Java

 
L'exécution est la suivante :

– Côté serveur :

lié
reçu : bonjour
reçu : au revoir

- Côté client:

appuyez sur n'importe quelle touche pour fermer


bonjour
serveur -> client : suivant ?
au revoir
serveur -> client : suivant ?
non

A noter que, pour les caractères, le langage Java utilise le type "char" basé
sur Unicode encodé en 16 bits. La transmission sur les réseaux est basée
sur un octet de 8 bits.

Les méthodes de télécommunication pour Java sont donc basées sur le transfert
d'octets.
– Convertir une String en bytes[ ] :
- chaîne de caractères = "ABCD" ;
- byte[ ] message=chaine.getBytes();
– Convertir un byte[ ] en String :
- chaine = new String(message);

4.2.1.4. Écriture directe sur le flux d'un socket


Le code suivant permet d'écrire directement sur le flux d'un socket :

OutputStream sockOut = socket.getOutputStream();


// Obtient l'écriture OutputStream de la socket.
// Nous l'utilisons directement pour écrire de nouveaux octets.
octet[ ] tampon = nouvel octet[1024] ;
Programmation distribuée en Java 81

 
buffer = "ABCD".getBytes();
essayez { sockOut.write(buffer);

// write(buffer) écrit tous les octets du buffer.


sockOut.flush(); // flush() devrait forcer l'écriture du contenu du tampon sur le flottant.

// N'attend pas que le tampon se remplisse. sockOut.write(tampon, 0, 2);

// write(buffer, pos, nbre) écrit le nombre d'octets (nbre) du


position indiquée (pos) .
} capture (IOException e) {}

L'écriture et le vidage peuvent déclencher une exception IOException particulière, une tentative de
écrire sur un flotteur fermé.

Dans le segment suivant, nous allons transférer diverses données en passant


par une seule chaîne de caractères :

essayez { OutputStream sockOut = socket.getOutputStream(); octet[ ] tampon = nouvel octet[1024] ; String envoi

} capture (IOException e) {}

4.2.1.5. Lire directement le flux d'un socket


Le code suivant permet une lecture directe du flux d'une socket :

InputStream sockIn = socket.getInputStream();


// Obtient le InputStream lu du socket.
octet[ ] tampon = nouvel octet[1024] ;
entier lu ;
essayez { lire = sockIn.read (tampon);

82 Programmation simultanée, temps réel et distribuée en Java

 
// lit le flux et le stocke dans le tampon. Renvoie le nombre de
lire les octets.
System.out.write(buffer, 0, lu);
// affiche le tampon sur la sortie standard.
} capture (IOException e) {}

La fin de flottant peut être normale (écriture fermée) ou accidentelle


(déconnexion de fin de processus d'écriture).

Les opérations de lecture peuvent lever une IOException.

4.2.1.6. Lecture et écriture de haut niveau


DataOutputStream permet d'écrire tout type de primitive Java
sur tous les systèmes.
Le code suivant écrit des données dans un format connu dans un socket :

DataOutputStream sockDataOut = null ;


essayer {
sockDataOut = new DataOutputStream(socket.getOutputStream());
sockDataOut.writeByte(6); //écrit le premier octet du nombre.
sockDataOut.writeChar('c'); //écrit le caractère qui correspond au
2 premiers octets. sockDataOut.writeBoolean(true); //écrit le booléen sur un octet. sockDataOut.writeInt(-1234); // écrit

float, s'il y a un tampon. sockDataOut.writeUTF("chaine String"); // écrit la chaîne en l'encodant

en UTF-8 modifié.
} capture (IOException e) {
}

Pour la lecture de données Java dans un format connu depuis une socket, il faut
utiliser DataInputStream qui nous permet de lire n'importe quel type de primitive Java sur tous
systèmes.

Programmation distribuée en Java 83

 
DataInputStream sockDataIn = null ;
essayez { sockDataIn = new DataInputStream(socket.getInputStream()); octet par = sockDataIn.readByte(); // renvoie l'o

// lit un octet et renvoie faux si l'octet est == 0. int i = sockDataIn.readInt(); // lit 4 octets et retourne le correspondant

Numéro. double d = sockDataIn.readDouble(); // lit 8 octets et retourne le

double correspondant. Chaîne s = sockDataIn.readUTF(); // décode les octets en caractères et

renvoie la chaîne suivante.


} capture (EOFException e) {
} capture (IOException e) {
}

L'une des méthodes ci-dessus lève EOFException si la fin du flottant est


atteint.

Pour écrire le texte dans une socket :

PrintWriter sockWriter = new PrintWriter(socket.getOutputStream()));


sockWriter.print("chaine String");
sockWriter.print(1234);
sockWriter.print(12.34);
sockWriter.println(true);

– Nous créons un PrintWriter attaché au OutputStream.


– On peut alors lire directement n'importe quel type Java avec print ou println.
– Aucune de ces méthodes ne génère d'exceptions.

Le code suivant permet à un utilisateur de lire ligne par ligne le texte d'un socket :

essayer{
BufferedReader sockReader = nouveau BufferedReader (nouveau InputStreamReader
(socket.getInputStream()))

84 Programmation simultanée, temps réel et distribuée en Java

 
Ligne de cordes ;
while ((line = sockReader.readLine()) != null) System.out.println(line);
} capture (IOException e) {}

– Nous créons un BufferedReader attaché au InputStream.


– On peut alors lire directement la chaîne de caractères avec readLine().
– readLine() renvoie la ligne lue sans le(s) caractère(s) de fin et null si
il arrive en fin de flottement.

4.2.1.7. Transmission d'objets à travers les prises


Dans le segment suivant, nous discuterons de la transmission d'objets via
les prises.

Exemple : un client se connecte à un serveur pour connaître la majeure et la


noter.

– Une table des élèves est stockée côté serveur.


– Un client se connecte au serveur et souhaite obtenir les informations d'un élève.
– Le client doit envoyer le nom de l'élève au serveur.
– Le serveur lit le nom, accuse réception, parcourt le tableau pour trouver
l'étudiant et enregistre enfin l'objet étudiant.
– Cet objet est envoyé au client à l'aide des sockets.
– L'objet est lu par le client (restauration).
– Le client affiche les caractéristiques de l'objet.
– L'objet transmis via le réseau doit être sérialisable.

importer java.io.Serializable ;
public class Student implémente Serializable{ String name; Corde majeure ; int moy;

Programmation distribuée en Java 85 Étudiant (String name, String major, int m


 
}
1
La classe Étudiant est partagée entre le client et le serveur
.

 1 Pour une version couleur des codes apparaissant dans ce chapitre, voir www.iste.co.uk/ benmammar/
java.zip

86 Programmation simultanée, temps réel et distribuée en Java

Exécution:

Du côté serveur:

lié
reçu un

Côté client:
serveur -> client : Etudiant : A GL : 13

Programmation distribuée en Java 87

 
4.2.1.8. Communications entre une applet Java et un serveur utilisant
prises
L'exécution suivante montre une communication entre une applet et un
serveur utilisant des sockets :

Illustration 4.4. Communication entre une applet et un serveur avec les Sockets. Pour un
version couleur de la figure, voir www.iste.co.uk/benmammar/java.zip

L'implémentation du serveur est la suivante :

importer java.io.* ;
importer java.net.* ;
importer java.util.* ;
public class NetServer { public static void main(String[] args) { try{ ServerSocket serverSocket = new ServerSocket(876

88 Programmation simultanée, temps réel et distribuée en Java InputStreamReader(clientSocket.getInputStream())); P


 
}

La mise en œuvre du client est la suivante :

import java.applet.Applet ;
import java.awt.*;
import java.awt.event.* ;
importer java.io.* ;
importer java.net.* ;
public class NetApplet étend Applet implémente ActionListener { TextField numbField; Affichage d'étiquettes ; Prise d

host");} catch (IOException e) { System.out.println("IO Exception");} numbField = new TextField(6); add(numbField); B

Programmation distribuée en Java 89 add(display); } public void actionPerform


 

l'événement if (e.getSource() instanceof Button) if (actionCommand.equals("Send")){ try {numb = Integer.parseInt(num

("Exception de format de nombre");} essayez { in = new BufferedReader(new InputStreamReader(socket.getInputStrea

Exception");} out.println(numb); essayez {numbStr = in.readLine();} catch (IOException ex) {System.out.println("Apple

}// fin de classe
4.2.2. Communication de haut niveau : middleware

Ce sont des couches offrant des services plus complexes. Ces calques sont créés
à l'aide de couches TCP/UDP.

90 Programmation simultanée, temps réel et distribuée en Java

 
Exemple : appel depuis une méthode dans une entité distante.

– Le middleware est une couche intermédiaire (couche logicielle) qui s'intègre


l'infrastructure de communication d'un réseau et les éléments de
application distribuée.

Figure 4.5. Position du middleware dans un réseau. Pour une version couleur de
la figure, voir www.iste.co.uk/benmammar/java.zip

– Les environnements distribués sont basés (pour la plupart) sur un RPC


mécanisme (appel de procédure à distance).
– Ce mécanisme fonctionne avec un mode requête/réponse.
– Le client fait une demande (demande un service).
– Le serveur traite alors la requête et, enfin, renvoie une réponse à
le client.

Le RPC possède les caractéristiques suivantes :

– Programmation procédurale, donc non orientée objet.


– Les paramètres et les valeurs de retour sont de type primitif.
– Pas de "référence lointaine".
Par la suite, le middleware a évolué de la manière suivante :
– JAVA RMI (Invocation de méthode à distance).

Programmation distribuée en Java 91

 
- Mono-langue ; Java, multiplateforme : de JVM à JVM.
- CORBA (Common Object Request Broker Architecture).
- Multi-langue, multi-plateforme.
- DCOM (Distributed Component Object Model) de Microsoft.
- Multi-langue, communication entre logiciels distribués
Composants.
Dans la section suivante, nous nous concentrerons sur Java RMI.

Figure 4.6. Opération RPC. Pour une version couleur du


figure, voir www.iste.co.uk/benmammar/java.zip

4.2.2.1. Présentation de l'IRM


RMI permet aux machines Java virtuelles de communiquer si elles sont allumées
la même machine ou sur deux machines distinctes.

java.rmi : API intégrée au JDK 1.1 et plus (depuis 1997).

Pour que les clients puissent accéder aux services distants, ils doivent être enregistrés dans un
enregistrement.

92 Programmation simultanée, temps réel et distribuée en Java

 
Le registre RMI s'appelle rmiregistry et il possède une table de hachage
où les clés sont des noms et les valeurs des objets distants.

Figure 4.7. rmiregistre

Le principe de fonctionnement d'une application RMI est le suivant :


– Le serveur crée les objets et les enregistre dans le rmiregistry.
– Cela permet aux clients de localiser les objets et d'obtenir des références vers
ces objets.
– Lorsqu'un client souhaite invoquer un objet à distance, il vérifie
rmiregistry
reçoit pour localiser
une référence l'objet
vers cet objet: ildistant
fournit(un
le nom
stub).de l'objet et
– Avec la référence nouvellement obtenue, le client pourra convoquer
les méthodes de cet objet.

Les méthodes du proxy B convoquent les méthodes réelles sur le distant


l'objet B en voyageant sur le réseau.

– Lorsqu'un client obtient une référence à un objet distribué, il obtient en


fait une référence à un stub.

Programmation distribuée en Java 93

 
– Un stub est un proxy (représentant local d'un objet distant) qui est chargé
dans le client lors de l'obtention de la référence.
– Le client convoque donc, via sa référence, les moyens qui résident
dans le stub qui convoque les méthodes réelles sur l'objet distant en voyageant
sur un réseau.
– C'est le mécanisme qui assure la transparence des appels.

Figure 4.8. Exploitation d'une application RMI. Pour une version couleur de
la figure, voir www.iste.co.uk/benmammar/java.zip

La figure 4.9 représente les étapes d'un appel de méthode distante.

4.2.2.2. Architecture RMI


La figure 4.10 représente l'architecture RMI.

94 Programmation simultanée, temps réel et distribuée en Java

 
Illustration 4.9. Étapes d'un appel de méthode distant. Pour une version couleur du
figure, voir www.iste.co.uk/benmammar/java.zip

Figure 4.10. Architecture IRM. Pour une version couleur de la figure, voir
www.iste.co.uk/benmammar/java.zip

Programmation distribuée en Java 95

 
– Première couche : talon/squelette.
- Stub : représentant local de l'objet distribué. - Initie une connexion avec la JVM distante en transmettant le

invocation distante à la couche d'objets de référence (RRL). - Assemble les paramètres en vue de leur transfert au

JVM distante. - Attend les résultats de l'invocation à distance, désassemble le

valeur ou l'exception renvoyée et renvoie la valeur en l'appelant.


- Squelette : utilisé pour localiser l'objet distribué. - Désassemble les paramètres de la méthode distante. - Appelle

votre interlocuteur.
- Remarques : Le squelette n'est plus nécessaire depuis Java 2 (1998).
- Une classe similaire de squelette générique est partagée entre tous
objets.
- De plus, jusqu'à la version 5.0 de J2SE (2004), un compilateur stub
appelé RMIC (Java RMI Compiler) était nécessaire pour générer le stub/
squelette avant de l'enregistrer dans le registre RMI.
- Désormais, il est possible de les générer dynamiquement (plus besoin d'utiliser le
CRIM).
– Deuxième couche : RRL (couche de référence d'objet).
- Permet d'obtenir la référence de l'objet distant en consultant le
rmiregistry.
- rmiregistry s'exécute sur chaque machine hébergeant des objets distants.
- Un seul rmiregistry par JVM.
- rmiregistry accepte les demandes de service sur le port 1099 (par défaut).
– Troisième couche : transport.
- Utilise un protocole de communication supérieur à TCP/IP.

96 Programmation simultanée, temps réel et distribuée en Java

 
- Lors d'un appel de méthode, le stub transmet l'appel à la couche RRL, la
ce dernier transmet la requête à la couche transport du RMI qui est au-dessus du
TCP/IP et transfère la requête en remontant jusqu'au squelette qui appelle
sur l'objet éloigné.
- Par défaut, RMI utilise le protocole JRMP (Java Remote Method
Protocol) sur le port 1099. Il s'agit d'un protocole utilisé pour appeler des méthodes distantes. - JRMP est le protocole d

applications écrites en Java - Version originale. - Intégré dans la langue. - Simple à utiliser.

- A partir de Java 2, et pour la compatibilité avec CORBA,


les communications entre le client et le serveur peuvent être effectuées à l'aide
le protocole IIOP (Internet Inter-ORB Protocol). - Version récente. - Compatible CORBA. - Plus difficile à mettre en œuvr

Dans ce cas, le package javax.rmi est requis

4.2.2.3. Déploiement de l'IRM


Un certain nombre de packages sont utilisés pour déployer RMI :
– java.rmi.Naming
– java.rmi.Remote
– java.rmi.RemoteException
– java.rmi.server.UnicastRemoteObject
– java.rmi.registry.LocateRegistry
– java.rmi.NotBoundException
java.rmi.Naming : est une classe finale : la classe finale publique Naming s'étend
Objet.
– Le serveur peut enregistrer un objet distant en utilisant le bind() (ou rebind())
méthode.

Programmation distribuée en Java 97

 
– lancements de liaison vide statique (nom de chaîne, obj distant)
DéjàBoundException, MalformedURLException, RemoteException.
– static void rebind (String name, Remote obj) lance RemoteException,
MalformedURLException.
– Remarque : bind() lève une exception si un objet est déjà enregistré sous
le même nom dans la base de registre (même nom pour deux objets différents).
Remote est une interface pour concevoir des objets distants.
– Le client trouvera l'objet grâce à la méthode lookup().
- la recherche à distance statique (nom de chaîne) lève NotBoundException,
MalformedURLException, RemoteException
Les éléments suivants déploieront RMI :
- Du côté serveur:
- La définition d'une interface contenant les méthodes pouvant être
appelé à distance (l'interface est partagée avec le client).
- Ecriture d'une classe qui implémente cette interface.
- Ecriture d'une classe (serveur) qui va instancier l'objet et l'enregistrer
en lui donnant un nom dans le registre RMI.
- Côté client:
- Obtenir une référence sur l'objet distant à partir de son nom.
- Méthode d'appel à partir de cette référence.
Pour déployer RMI, suivez les étapes ci-dessous :

Interface partagée entre le client et le serveur :

L'interface doit étendre java.rmi.Remote.

importer java.rmi.Remote ;
import java.rmi.RemoteException ; interface publique Hello étend Remote { public String Bonjour() lance RemoteEx

98 Programmation simultanée, temps réel et distribuée en Java

 
– Remote est une interface sans méthode.
– Un objet qui implémente l'interface Hello est un objet qui prend en charge
un accès distant à ses méthodes.
– Toutes les méthodes doivent lancer une RemoteException.

Figure 4.11. Déploiement RMI. Pour une version couleur de la figure, voir
www.iste.co.uk/benmammar/java.zip
Implémentation de la classe qui implémente l'interface côté serveur :

import java.rmi.RemoteException ;
importer java.rmi.server.UnicastRemoteObject ;
la classe publique HelloImpl étend UnicastRemoteObject implémente Hello
{ public HelloImpl() lance RemoteException { }

public String Bonjour() lance RemoteException { return "Bonjour tous le monde !"; }

Programmation distribuée en Java 99

 
– Il doit implémenter l'interface Hello.
– Il doit étendre UnicastRemoteObject.
– Unicast : l'objet distant existe en un seul exemplaire sur un seul
machine.
– Il peut implémenter d'autres méthodes que celle de l'interface, mais celles-ci
ne sera pas accessible à distance.
– Les builders lancent une RemoteException.
– Par conséquent, l'utilisateur doit toujours en écrire un.

Implémentation du serveur :

import java.net.MalformedURLException ;
import java.rmi.Naming ;
import java.rmi.RemoteException ;
importer java.rmi.registry.LocateRegistry ;
public class HelloServer { public static void main(String[] args) { try {

LocateRegistry.createRegistry(1099); } catch (RemoteException e1) { System.err.println("rmiregistry est déjà lanc


100 Programmation simultanée, temps réel et distribuée en Java throw new InternalError("l'URL est incorrecte"); } }
 

Le format général d'une URL RMI est rmi://host:port/Nom.


Exemple : Naming.rebind("rmi://localhost:1099/Hello",hello).
– Seul le Nom est obligatoire.
– Le protocole par défaut est rmi.
– Le port par défaut est 1099.
– L'hôte par défaut est localhost.
Exemples d'URL valides :
– Nom (port 1099 sur localhost).
– //172.16.2.125/Nom (port = 1099).
– //172.16.2.125:2000/Nom.

Implémentation du client :

import java.net.MalformedURLException ;
import java.rmi.Naming ;
importer java.rmi.NotBoundException ;
import java.rmi.RemoteException ;
public class HelloClient { public static void main(String[] args) { String url = null; Bonjour bonjour = null; essayez {

} catch (MalformedURLException e) { System.err.println("L'URL " + url + "est incorrecte");

System.exit(1); } catch (RemoteException e) {

Programmation distribuée en Java 101 System.err.println("Avez-vous lancé le rm


 

essayez { System.out.println(hello.Hello()); // appelez la méthode distante } catch (RemoteException e) { System.err

– Lancer le serveur.
– Lancer le client.
– Exécution côté client :
Bonjour tout le monde !
Pour implémenter une application RMI, vous devez d'abord :
– Écrivez la ou les interfaces distantes.
- Bonjour.java.
– Ecrire sa (leurs) implémentation(s).
- BonjourImpl.java.
– Écrivez le serveur.
- HelloServer.java.
– Écrivez le client.
- BonjourClient.java.
Remarque : il est possible de regrouper les deux classes (serveur et interface
implémentation) dans la même classe.

102 Programmation simultanée, temps réel et distribuée en Java

 
L'exemple suivant décrit ce type de cas. C'est une télécommande
multiplication.

L'interface:

importer java.rmi.Remote ;
import java.rmi.RemoteException ;
interface publique bonjour étend à distance {
public int multi (int a, int b) lève RemoteException ;
}

Implémentation du serveur :

import java.rmi.*;
import java.rmi.server.* ;
import java.rmi.registry.* ;
la classe publique HelloServer étend les implémentations d'UnicastRemoteObject
Bonjour {
public HelloServer() lève RemoteException {}
public int multi(int a, int b) lance RemoteException {
retourner un * b ; }
public static void main(String[] args) { essayez { LocateRegistry.createRegistry(1099); } catch (RemoteException e1

objet HelloServer = new HelloServer();


Naming.rebind("badr",object);
System.out.println("Serveur prêt");
}
Programmation distribuée en Java 103

 
catch(Exception e) {
System.err.println("Erreur : " + e.getMessage());
}
}
}

Quelques notes concernant bind et rebind :

Le code suivant est correct :

Objet HelloServer = new HelloServer();


Naming.rebind("badr",object);
Naming.rebind("badr1",object);

Il y a la possibilité d'attribuer plus d'un nom au même


objet : lier ou relier indépendamment.

Le code suivant génère une exception :

Objet HelloServer = new HelloServer();


HelloServer object1 = new HelloServer();
Naming.bind("badr",object);
Naming.bind("badr",object1);

Le code suivant est correct :

Objet HelloServer = new HelloServer();


HelloServer object1 = new HelloServer();
Naming.rebind("badr",object);
Naming.rebind("badr",object1);

Cela fonctionne avec un rebind car il s'agit d'un resave; badr se réfère uniquement à object1.

Implémentation du client :

import java.rmi.*;
classe publique BonjourClient {
public static void main(String[] args) {
essayer {

104 Programmation simultanée, temps réel et distribuée en Java Hello reference = (Hello)Naming.lookup("badr"); int r
 

catch(Exception e) { System.err.println("Erreur : " + e.getMessage()); }


}
}

Exécution:

– Côté serveur : serveur prêt.

– Côté client : La multiplication est 60.

La classe suivante est utile pour générer le registre :


– la classe finale publique LocateRegistry étend l'objet.
– Classe utilisée pour générer le rmiregistry dans le package java.rmi.registry.
– méthode createRegistry :
- Registre statique createRegistry (port int) : crée un registre.
- LocateRegistry.createRegistry(1099) : crée un registre sur le port 1099.
- createRegistry est une méthode statique qui retourne un Registry.
- L'interface publique Registry s'étend à distance.
- Dans le package java.rmi.registry.
- LocateRegistry.createRegistry(1099) : créer un registre distant
(registre).
– méthode getRegistry :
- Renvoie une référence vers le registre distant.
- Diverses signatures : - Registre statique getRegistry(). - Registre statique getRegistry (port int). - Registre statique

Programmation distribuée en Java 105 - Registre statique getRegistry(String hos


 

RMIClientSocketFactory csf)

Exemple d'application :

Chaîne [ ] NamesSaved = LocateRegistry.getRegistry("localhost",


1099).list();
liste() : méthode de l'interface Registry qui renvoie la liste des noms
enregistré dans le registre.

4.2.2.4. Echanges de paramètres dans RMI


Les paramètres échangés via le Java RMI doivent être :

– Types primitifs : int, short, double, boolean …, aussi String.


– Objets sérialisables.
– Objets distants.
Les types primitifs sont transmis par valeur (copie).
Les objets Serializable sont sérialisés, transmis, puis désérialisés
(transmis en valeur).
Les objets distants sont transmis sous forme de références distantes.
– Types primitifs :

Le code suivant représente l'implémentation de la méthode « multi »


côté serveur :

public int multi (int a, int b) lance RemoteException { return a * b;

L'appel de méthode côté client est le suivant :

essayez { Hello reference = (Hello)Naming.lookup("badr"); int result = reference.multi(5,12); System.out.println("La mu

}catch(Exception e) { System.err.println("Erreur : " + e.getMessage()); }


106 Programmation simultanée, temps réel et distribuée en Java

 
Dans l'exemple précédent, l'appel est effectué par valeur.

– Transmission d'un objet Serializable :


Exemple : on souhaite utiliser un serveur de données pour stocker un jeu de coordonnées 2D
décrivant les positions d'un certain nombre de véhicules dans un avion.
Ce serveur devra inclure les fonctionnalités suivantes :
– Créer un nouveau poste et lui attribuer un nom.
– Accès en lecture aux positions.
– Accès au nombre de positions.
– Le serveur doit implémenter une table de hachage.
– En java, la classe Hashtable est celle qui se trouve dans le package java.util.

Figure 4.12. rmiregistry. Pour une version couleur de la figure, voir


www.iste.co.uk/benmammar/java.zip

Programmation distribuée en Java 107

 
Dans notre cas:

– Les clés sont les noms donnés aux positions.

– Les valeurs sont les coordonnées 2D des véhicules.

– L'interface partagée est la suivante :

import java.rmi.*; interface publique La position s'étend à distance { public void insererTable (Point p, String no

RemoteException ; public Point position (String nom) lance RemoteException ; public int nombreCles() lève Rem

}
L'interface précédente implémente les éléments suivants :
– Création d'un nouveau poste et attribution d'un nom.
– Accès en lecture aux positions.
– Accès au nombre de positions.

La classe Point doit implémenter Serializable.

importer java.io.Serializable ;
public class Point implements Serializable { public float x; flotteur public y ; point public(float xx,float yy) { x = x

public String toString() { return("("+x+", "+y+")") ; }

108 Programmation simultanée, temps réel et distribuée en Java

 
L'implémentation du serveur :

import java.rmi.*;
import java.rmi.server.* ;
importer java.net.* ;
importer java.util.Hashtable ;
import java.rmi.registry.* ;
public class Serveur étend UnicastRemoteObject implémente Position
{ table de hachage privée h ;

public Serveur() lance RemoteException { h = new Hashtable() ; } public void insererTable(Point p,String nom) lan

{ h.put(nom,p); }

public Point position(String nom) lance RemoteException { return((Point) h.get(nom)); } public int nombreCles() la

public static void main(String [] args) { try { Serveur ib = new Serveur(); essayer {

LocateRegistry.createRegistry(1099); } catch (RemoteException e1) { System.err.println("rmiregistry est déjà lancé


Programmation distribuée en Java 109 System.out.println(re) ; } catch(Malform
 

La mise en œuvre du client :

importer java.rmi.* ; public class Client { public static void main(String [] args) { try { Position b =(Position) Namin

Exécution:

- Du côté serveur:
Prêt

- Côté client:
0
2
(11.0, 5.0)
(14.0, 9.0)

110 Programmation simultanée, temps réel et distribuée en Java

 
Ignorer Serializable dans la définition de point comme suit :

public class Point { public float x ; flotteur public y ; point public(float xx,float yy) { x = xx ; y = yy ; }

public String toString() { return("("+x+", "+y+")") ; }


}

nous donnera l'exécution suivante :

- Du côté serveur:

Prêt // La transmission n'a pas encore eu lieu.

- Côté client:
0 // le type primitif int ne pose pas de problème. java.rmi.MarshalException : erreur lors du regroupement des argu

l'exception est : java.io.NotSerializableException : Position

Le problème survient pour la transmission d'un objet de type Point qui doit être
Sérialisable.
Transmission d'objets Distants : Serveurs multi-threads RMI (chatrooms) :
– Chaque client est un fil et a trois actions possibles : rejoindre, parler ou quitter.
– Le serveur doit maintenant maintenir une liste d'objets distribués (Remote).
– Utilisez la synchronisation pour garantir la cohérence entre les données.

Programmation distribuée en Java 111

 
– Les actions du client doivent être affichées aux autres clients.
– Thread implémenté à l'aide de Jframe (Swing)

Première cliente :

Deuxième client :
112 Programmation simultanée, temps réel et distribuée en Java

 
Troisième client :

Lorsque le troisième client se déconnecte, le premier client doit voir ceci :

Programmation distribuée en Java 113

 
Le deuxième client sera le suivant :
import java.rmi.*;
interface publique ChatInterface extend Remote { public void rejoindre (Notify n) throws RemoteException; public vo

}
import java.rmi.*;
public interface Notify extend Remote { // Notify fait référence à un client public String getName() lance RemoteExcep

RemoteException ; public void exitMessage (String name) lève RemoteException ;

} // nom est le nom du client.

Côté serveur :

public class Serveur étend UnicastRemoteObject implémente


Interface de chat {

114 Programmation simultanée, temps réel et distribuée en Java

 
collection privée<Notify> threadList = new ArrayList<Notify>(); public Serveur() lève RemoteException {}

jointure vide synchronisée publique (Notify n) lance RemoteException {


threadList.add(n);
// parcourir la liste
for (Iterator i = threadList.iterator();i.hasNext(); ) {
Notifier le client = (Notifier)i.next(); // récupère l'élément dans la liste
client.joinMessage(n.getName()); } }


Collection est une interface implémentée par ArrayList.

iterator() est une méthode Collection qui renvoie un Iterator sur la table (Iterator
est une interface pour désigner un itérateur sur la table).
hasNext() et next() sont des méthodes de l'itérateur.

Partie de la classe qui implémente Notify :


Programmation distribuée en Java 115

 
4.2.2.5. Gestion de la sécurité dans RMI
La sécurité est importante lorsque le code est téléchargé (il peut être risqué d'exécuter
code d'un serveur distant). Pouvoir transmettre du code exécutable à distance
est, en théorie, une faille de sécurité. Lors de la transmission locale du code, il n'y a pas
problème de sécurité.

- La solution:
- Mettre en place un « gestionnaire de sécurité ».

– Solution générale en Java :


- Utilisez la classe java.lang.SecurityManager (étend Object).

- Cela permet de spécifier les autorisations d'accès aux fichiers, réseaux,


etc.

– Solution spécifique RMI :


- Utilisez la classe java.rmi.RMISecurityManager.

- la classe publique RMISecurityManager étend SecurityManager.

Avec RMI, il existe deux manières de gérer la sécurité :

– Utilisez une sous-classe de SecurityManager au lieu de RMISecurityManager.

public class SecurityManagerOuvert étend SecurityManager{


public void checkPermission(Permission perm) { }
}

– Utilisez RMISecurityManager en conjonction avec (à partir de Java 2) un fichier


décrivant la politique de permissions : fichier « java.policy ».

Le fichier java.policy définit la politique de sécurité afin de spécifier


gestes autorisés.

Dans : C:\Program Files (x86)\Java\jdk1.8.0_25\jre\lib\security.

116 Programmation simultanée, temps réel et distribuée en Java

 
Exemple de contenu dans le fichier java.policy :

accorder { autorisation java.security.AllPermission ;

} ;

Il autorise tous les appels de méthode (pas de politique de sécurité).


accorder {
autorisation java.net.SocketPermission "*:1024-65535", "connecter,
J'accepte";
permission java.net.SocketPermission "*:80", "connect" ;
autorisation java.io.FilePermission "C:\\temp\\m2rsd.txt", "lire" ;
permission java.io.FilePermission "C:\\temp\\test.exe", "lire, écrire,
supprimer, exécuter" ;
} ;

Il permet les connexions socket sur les ports 1024 à 65535 pour tous les clients RMI
et les connexions au serveur Web par défaut (port 80) sur toutes les machines. Ça aussi
définit les droits d'accès par rapport aux fichiers cités.

Pour installer un gestionnaire de sécurité, exécutez le code suivant :


System.setSecurityManager(nouveau SecurityManager()));

Exemple:

public static void main (String args[]) { // serveur


System.setSecurityManager (nouveau SecurityManager ()) ;
essayer {
LocateRegistry.createRegistry(1099); } catch (RemoteException e1) { System.err.println("rmiregistry est déjà lancé

Programmation distribuée en Java 117

 
Pour utiliser java.policy :

$ javac Serveur.java
$ java -Djava.security.policy=java.policy Serveur

Nous pouvons également utiliser RMI avec TLS :


– Transport Layer Security (TLS), anciennement connu sous le nom de Secure Sockets
Layer (SSL), est un protocole de sécurité pour les échanges sur Internet.
– Depuis J2SE 5.0 (2004), il est possible d'utiliser TLS avec RMI.
– TLS agit comme une couche supplémentaire qui assure la sécurité des données, située
entre la couche application et la couche transport.

Objectif:
– Authentification du serveur.
– En option, authentification du client.
– Confidentialité des données (ou session cryptée).
– Intégrité des données échangées.
– L'API JSSE (Java Secure Socket Extension) permet de
manipuler des sockets sécurisés en Java répondant aux spécifications SSL.
– Les classes et interfaces JSSE sont regroupées dans des packages javax.net
et javax.net.ssl.
Figure 4.13. RMI avec TLS. Pour une version couleur de la figure, voir
www.iste.co.uk/benmammar/java.zip

118 Programmation simultanée, temps réel et distribuée en Java

 
4.2.2.6. Chargement de classe dynamique
– Les classes Java sont hébergées sur un serveur Web.
– Cela évite de conserver toutes les définitions de classe localement.
– Les mêmes fichiers de classe sont partagés par tous les clients.
– Seules les classes nécessaires sont chargées.
– Dans ce cas, on peut se tourner vers le chargement dynamique :
- Placez la classe java (stub, par exemple) dans un serveur Web qui est
accessibles par le client. Par exemple : http://monserveur.fr/test.
- Implémentez un SecurityManager : - Ajoutez la ligne suivante dans le client et le serveur :

System.setSecurityManager(nouveau SecurityManager())); - Exécutez ce qui suit sur le serveur : java -

Djava.security.policy=java.policy Server - Exécute le client en fournissant l'URL de chargement de classe dynamique :

Le stub côté serveur est le suivant :

import java.rmi.Naming ;
classe publique BonjourClient {
public BonjourClient() {
essayer {
Bonjour obj = (Bonjour)Naming.lookup("Bonjour");
System.out.println(obj.Bonjour());
} capture (Exception e) {

Programmation distribuée en Java 119

 
System.out.println("Exception HelloClient : " + e);
}
}
}
HelloClient.java est une classe partagée entre plusieurs clients RMI.

Le code suivant représente un client RMI utilisant une classe dynamique


Chargement en cours:

importer java.rmi.server.RMIClassLoader ;
importer java.util.Properties ;
classe publique DynamicClient {
public DynamicClient() lance une exception {
// Installer un gestionnaire de sécurité (sans ce gestionnaire, le chargement n'est pas autorisé)
System.setSecurityManager(nouveau SecurityManager());
Propriétés p = System.getProperties();
/Indiquer le chemin de téléchargement
URL de chaîne = p.getProperty("java.rmi.server.codebase");
// Télécharger la classe souhaitée
Classe clientClass = RMIClassLoader.loadClass(url, "HelloClient");
// Crée une instance de classe
clientClass.newInstance();
}
public static void main (String args[]) {
essayez {DynamicClient dc = new DynamicClient();
} catch (Exception e) {System.out.println(e);}
}
}

4.2.2.7. Asynchronicité dans RMI : méthodes de rappel


Dans RMI, le client appelle une méthode située côté serveur.

Prérequis : lors de l'exécution, le serveur peut lancer des processus sur le


côté client.

120 Programmation simultanée, temps réel et distribuée en Java

 
Effectuer un appel du serveur vers les clients.

Objectif : augmenter le dynamisme.

Dans ce cas, chaque élément (client ou serveur) peut jouer les deux rôles
indifféremment (client et serveur simultanément).

Les appels du serveur vers le client sont appelés rappels.


– Étant donné que le serveur ne connaît pas la façon dont ce type de méthode est
implémenté et que son temps d'exécution peut être long, les callbacks
généralement être mis en œuvre sous forme d'opérations asynchrones.
– Le serveur évite de pénaliser les autres clients avec un appel synchrone.
– Appel synchrone → Exécution bloquée en attente de résultat.
– Le callback est un mécanisme qui permet l'introduction de
asynchronicité dans RMI, dont les invocations sont naturellement synchrones et
blocage.
– Une méthode asynchrone est toujours vide (ne renvoie aucun résultat).
– Le programme effectuant l'appel n'a pas besoin d'un résultat immédiat ; sinon,
il sera bloqué.
– En Java, les méthodes sont synchrones à l'exception de la méthode start() d'un
fil de discussion.
– start() ne bloque pas le thread appelant.
– Le client passe l'objet à rappeler (objet distant) en paramètre
au serveur.
– Le client laisse ses coordonnées pour être rappelé ultérieurement.
– Première interface : ServerInterface (implémentation côté serveur).
– Le serveur exécute un appel asynchrone sur l'objet.
– Un thread doit effectuer le rappel.
– Deuxième interface : InterfaceCallback (implémentation côté client).

Programmation distribuée en Java 121

Illustration 4.14. Rappel dans RMI. Pour une version couleur de la figure, voir
www.iste.co.uk/benmammar/java.zip

Pour effectuer le rappel, nous avons besoin de six classes comme suit :

Figure 4.15. Classes nécessaires pour un rappel. Pour une version couleur de
la figure, voir www.iste.co.uk/benmammar/java.zip

122 Programmation simultanée, temps réel et distribuée en Java

 
InterfaceCalback.java : interface contenant la méthode de rappel. Cette
L'interface est implémentée par Callback.java.
Un exemple de cette interface est le suivant :

importer java.rmi.Remote ;
import java.rmi.RemoteException ;
interface publique InterfaceCallback étend Remote { public void doCallback () lance RemoteException ;

C'est l'interface de la méthode de rappel qui est implémentée sur le client


côté.

Callback.java : la classe qui implémente la méthode de rappel. Cette classe


est disponible uniquement côté client pour implémenter la méthode de rappel.

import java.rmi.RemoteException ;
import java.rmi.server.UnicastRemoteObject ;
la classe publique Callback étend les implémentations d'UnicastRemoteObject
Rappel d'interface {
public Callback() lève RemoteException {}
public void doCallback() throws RemoteException { System.out.println ("Bonjour tous le monde") ;

}
}
Servant.java : le thread qui lance la méthode de rappel.

import java.rmi.*;
public class Servant étend Thread {
obj de rappel d'interface privé ;
serviteur public (obj InterfaceCallback) {
this.obj = obj ;
}
public void run() { // exécution en tant que thread séparé
essayez {Thread.sleep (3000) ; } catch ( InterruptedException e ) { }

Programmation distribuée en Java 123

 
essayez {obj.doCallback() ; // exécute le callback, appelle la méthode sur le
côté client
}
catch (RemoteException e ) { System.err.println ("Echec du retour d'appel : " + e ) ;

}
}
}

InterfaceServer.java : interface contenant la méthode utilisée par le serveur


pour rappeler le client, le paramètre de cette méthode est l'objet à
rappeler. Cette interface est implémentée par Server.java.

importer java.rmi.Remote ;
import java.rmi.RemoteException ;
interface publique InterfaceServeur extend Remote { public void callMeBack (InterfaceCallback obj) throws RemoteEx

;
}

La méthode callMeBack est utilisée par le client.

Client.java : crée l'objet et le passe au serveur (appelez la méthode


de l'InterfaceServer qui est implémenté côté serveur).
Faire un RMI classique pour être rappelé :

import java.rmi.Naming ;
Client de classe publique {
public static void main( String [ ] args ) lance une exception {
Rappel obj = nouveau Rappel ( ) ; // création de l'objet callback
InterfaceServeur serv= (InterfaceServeur) Naming.lookup ("Serveur");
System.out.println ("Démarrage de l'appel");
serv.callMeBack (obj) ; // demande d'appel
}
}

124 Programmation simultanée, temps réel et distribuée en Java

 
Server.java : implémente la méthode qui rappelle le client en passant le
objet reçu de ce dernier fil.

import java.rmi.Naming ;
import java.rmi.RemoteException ;
importer java.rmi.registry.LocateRegistry ;
importer java.rmi.server.UnicastRemoteObject ;
public class Server étend UnicastRemoteObject implémente
Serveur d'interface {
public Server ( ) lève RemoteException { }
public void callMeBack (InterfaceCallback obj) lance RemoteException {
Serviteur serviteur = nouveau Serviteur (obj) ; // Création du fil
serviteur.start ( ) ; // Initialisation du thread
}
public static void main( String [ ] args ) lance une exception {
essayez {LocateRegistry.createRegistry(1099);} catch (RemoteException e1) { System.err.println("rmiregistry est déjà la

port");System.exit(1); }
Serveur serv = nouveau Serveur ( ) ;
Naming.rebind ("Serveur" , serv) ;
System.out.println ("Serveur prêt" ) ;
}
}

Exécution:

Côté serveur : prêt pour le serveur.

Côté client : démarrage de l'appel.


Bonjour à tous.

Programmation distribuée en Java 125


 
4.2.2.8. Ramasse-miettes distribué dans RMI
– Difficulté : environnement distribué donc références distantes.
– Objectif : éviter les pertes de messages de référencement.

Un objet distant est rendu disponible pour le DGC (ordures distribuées


collecteur), s'il n'y a plus de stubs présents.
– Avec RMI : double mécanisme géré par :
- Comptage de références : nombre de clients comptant l'objet. - Chaque transmission de référence +1. - Chaque fin

- Bail : Mémoire « louée » à un objet pour une durée limitée. - Par défaut 600000 millisecondes (10 min), modifiabl

propriété java.rmi.dgc.leaseValue (à partir de la version 1.1 du JDK). - Pour le modifier, faites-le au lancement du prog

$ java -Djava.rmi.dgc.leaseValue=20000 Serveur.


Si le compteur tombe en dessous de 0 ou si le bail expire, l'objet devient un
candidat à la DGC.

annexe

Exercice 1 : cycle de vie d'un thread

Considérez la classe suivante :

la classe Parrot4 étend Thread { chaîne privée cri = null ; int privé fois = 0 ; public Parrot4 (String s, int i) { cri = s; fois =
}

class ChatAndLaunchParrot4{ public static void main(String args[]) { Parrot4 parrot = new Parrot4("coco",5);

128 Programmation concurrente, temps réel et distribuée en Java parrot.start(); System.out.println("Thread bavard : "
 

Question : Lancer l'exécution du programme ci-dessus.

Quel est le nom du thread principal, quel est le nom donné par
par défaut à l'autre fil? Quel est l'intérêt de la méthode isAlive() ? Indice:
la méthode de classe currentThread() retourne un pointeur sur l'objet Thread,
qui appelle cette méthode.

Exercice 2 : propriétés de différents threads

Considérez les deux classes suivantes :

la classe Parrot5 étend Thread { chaîne privée cri = null; int privé fois = 0 ; public Parrot5(String s, int i) { super("perro

}
public void run(){ afficheThreads();
Annexe 129 for (int n=0; n<fois; n++) { try { Thread.sleep(1000);
 

class ChatAndLaunchParrot5 { public static void main(String args[]) { Thread.currentThread().setName("chatty"); Parr

Astuce : La méthode setName (String name) permet de nommer un


fil de discussion. Le constructeur de Thread offre la possibilité de le nommer comme suit :
Thread (nom de la chaîne).

La méthode de classe activeCount() donne le nombre de threads actifs dans le


groupe du thread appelant.

130 Programmation simultanée, temps réel et distribuée en Java

 
La méthode de classe énumère (table Thread[]) stocke dans la table donnée le
références des threads actifs dans le groupe et les sous-groupes du thread appelant.
Il renvoie le nombre de threads actifs obtenus.

Question : lancer l'exécution du programme précédent.

La procédure displayThreads() est appelée avant et après la boucle pour le


méthode run().
Les threads actifs avant la boucle étaient :
Numéro 0 Sujet : bavard
1er sujet : perroquet
Après la boucle for, ils sont devenus :
Fil numéro 0 : perroquet
1er fil : Détruire JavaVM

Expliquez ce résultat, qu'est-ce que DestroyJavaVM ?

Exercice 3 : quatre compteurs en parallèle


L'objectif de cet exercice est de créer quatre compteurs s'exécutant en
parallèle.

Chaque "compteur" a un nom (Toto par exemple) et il compte de 1 à 10.


Il fait une pause aléatoire entre chaque numéro (0 à 5000 millisecondes pour
Exemple). Chaque compteur affiche un nombre (par exemple, Toto affichera,
"Toto : 3"); n affiche un message du type "Toto a fini de compter jusqu'à 10"
quand c'est fini.

La classe compteur possède un seul attribut, qui est le nom du


Compteur de type chaîne.

Écrivez la classe compteur et testez-la en lançant 4 qui comptent jusqu'à 10. Voir
lequel termine premier (les quatre jetons s'appellent Tata, Titi, Toto, Tutu).

Faire 2 versions : une où les threads sont créés avec un Thread


fille-classe, et une où ils sont créés avec une instance d'un
classe qui implémente Runnable.

Annexe 131

 
Exercice 4 : ressource en exclusion mutuelle

Considérez les trois classes Java suivantes :

public class Printer1 { texte de chaîne privé ; public Printer1() { text="";

} public void print(String t) { text=t; for (int j=0;j<text.length()-1; j++) { System.out.print(text.charAt(j)); essayez { Threa

System.out.println(text.charAt(text.length()-1)); }

public class Prog56 { public static void main (String argv[]) { Writer2 writerA, writerB ; Imprimante1 impression= nou

public class Writer2 extend Thread { private String text; impression privée Printer1 ; public Writer2(String t, Printer1
132 Programmation simultanée, temps réel et distribuée en Java text=t; } public void run() { for (int i=0; i<10; i++) { pri
 

– Dans la classe Printer1, décrivez le comportement de la méthode d'impression.


– Décrire le comportement d'un thread de type Writer2 (expliquer le run()
méthode).
– Lancer l'exécution de Prog56. Pourquoi le résultat des tirages
illisible?
– Changer dans la classe Printer1 la signature de la méthode d'impression en public
synchronisé void print (String t) et redémarrez l'exécution de Prog56.
– Quel est l'intérêt de la méthode synchronisée ?

Exercice 5 : programmer une tâche avec un délai initial et une périodicité

Nous souhaitons apprendre à un élève la table de multiplication pour 7, et c'est pourquoi


on veut programmer une tâche qui affiche la table de multiplication de 7 tous les 4
secondes et avec un retard initial de 2 secondes. Le résultat est obtenu dans le
formulaire suivant :

Voulez-vous toujours apprendre la table de multiplication ? (o/n)

La table de multiplication pour 7 est :


1*7=7
2 * 7 = 14
3 * 7 = 21

Annexe 133

 
4 * 7 = 28
5 * 7 = 35
6 * 7 = 42
7 * 7 = 49
8 * 7 = 56
9 * 7 = 63
10 * 7 = 70
PS : Toute réponse autre que « y » annule la programmation.

Exercice 6 : variables partagées : classe interne

Considérons les deux classes suivantes (la classe Parrot20 est dite interne
car il est situé à l'intérieur de la classe MathsParrots20), et il y a un
fichier java unique appelé MathsParrots20.java.
classe publique MathsParrots20{ compteur int privé ; public static void main(String args[]) { new MathsParrots20(); } p

134 Programmation concurrente, temps réel et distribuée en Java private int fois = 0; public Parrot20(String s, int i) { c
 

– Lancer et examiner l'exécution.


– Astuce : join() est utilisé pour afficher la valeur du compteur avant
la fin des deux fils. En raison des règles de visibilité en Java, le compteur
variable est visible/accessible depuis la classe Parrot20 et donc pour les deux
threads parrotA et parrotB. Les deux threads accèdent donc à un espace partagé
commun à toutes les variables, contrairement aux processus qui possèdent leur propre travail
espace séparé des autres processus.

– Réutiliser la simulation d'une course pour simuler une course entre cinq coureurs
A, B, C, D et E et affiche le rang de chaque coureur.
Exemple:

– A est arrivé à la première place


– B est arrivé à la deuxième place
– D est arrivé à la troisième place
–…
Annexe 135

 
Exercice 7 : variables partagées : variable globale (statique)

Avant de commencer : sous Java, chaque lettre peut être codée via le type char
qui a aussi une valeur entière. Testez et examinez le code suivant :

System.out.println('A');
System.out.println('A'+0);
System.out.println('A'+1);
System.out.println((char)('A' + 0));
System.out.println((char)('A' + 1));
System.out.println("Afficher l'alphabet :"); for (int i = 0; i < 26; i++) { System.out.print((char)('A' + i)+" "); }

– Reprendre l'exemple d'une course pour simuler une course entre cinq coureurs A,
B, C, D et E et affiche le rang de chaque coureur. Vous devez utiliser une boucle pour
lancer les cinq threads.
Exemple d'exécution :
– A est arrivé à la première place
– B est arrivé à la deuxième place
– D est arrivé à la troisième place
–…
– Simuler les quatre threads A, B, C et D, qui calculent respectivement
la factorielle de 4, 5, 6 et 7. Si l'ordre de terminaison est A, B, C et D, vous
doit également afficher :

A est très rapide


B est rapide
C est moyen
D est lent

136 Programmation simultanée, temps réel et distribuée en Java

 
Exercice 8 : synchronisation des threads

Cet exercice traite des problèmes d'accès simultané à une banque


compte (partage des ressources en général). Pour la classe Counter suivante, son
méthode Nulloperation est censée laisser le niveau à 0.

public class Counter { private int balance = 0; public void operationNulle(int sum) { balance += sum; System.out.p
}

Considérez la classe suivante qui contient le thread Operation et le


fil principal :

L'opération de classe publique étend le fil { Compteur privé Compteur ; public Operation(String nom, Counter C

public void run() { while (true) { int i = (int) (Math.random() * 1001); Nom de chaîne = getName(); System.out.pr

Annexe 137 System.out.println(" Je suis thread " + nom + ":**bal


 

solde); System.exit(1); } } }

public static void main(String[] args) { Counter Counter = new Counter(); for (int i = 0; i < 3; i++) { Operation ope

Compteur); opération.start(); } }

Question : examinez le code et exécutez la classe Operation.

Déterminez le problème rencontré : operationNull doit laisser le


Contrepoids inchangé (à 0), et pourtant, au bout d'un moment, le solde
change à partir de 0. Expliquez.

Modifiez le code pour éviter ce problème.

Exercice 9 : applet et thread

Une applet est un petit Java destiné à être téléchargé et exécuté


via un navigateur Web. Une fois compilée, une applet doit être appelée dans un html
dossier. Ensuite, pour être exécuté, le fichier html sera visualisé dans un
navigateur ou dans l'outil appletviewer.

Copiez le code suivant dans un fichier nommé Prog11.java.

importer java.awt.Graphics ;
import java.applet.Applet ;
la classe publique Prog11 étend l'applet {
public void paint (Graphics g) { g.drawLine (10, 30, 200, 30); g.drawRect (20, 40, 100, 50); g.drawOvale (140, 40, 50, 50);
 

Notre classe hérite de la classe Applet : il suffit de redéfinir la méthode paint


en utilisant la méthode draw de la classe Graphics.

Copiez le code suivant dans un fichier intitulé dessin.html

<html> <body> <applet code="Prog11.class" width=350 height=150>


</applet> </body></html>

Exécutez drawing.html (en cliquant directement sur le fichier), ou la commande-


ligne en utilisant appletviewer drawing.html. Vous pouvez également exécuter Prog11.java
sous Netbeans. Pour le navigateur, assurez-vous simplement que le plugin java est présent.

Expliquer les différentes méthodes de peinture.

Considérez la classe suivante :

importer java.awt.Graphics ;
importer java.awt.Color ;
import java.applet.Applet ;
public class Prog12 étend Applet { public void init() { setSize(220,120); setForeground(Color.red); setBackground(Color

Annexe 139 g.drawString("text draw", 10, 60);


 

}
}

La méthode init n'est appelée qu'une seule fois à la création de l'applet.

Initialisation de l'applet : modification de sa taille, et du fond


et couleurs de dessin.

Exécutez l'applet.
Expliquez les différentes méthodes dans init et paint.
Considérez la classe CounterThread suivante :

la classe publique CounterThread étend l'applet implémente Runnable { Thread t; nombre entier ; public void init() { C

} public void run() { while (Count < 20) { Count++; repeindre(); essayez {Thread.sleep(1000); } catch (InterruptedExcep

140 Programmation simultanée, temps réel et distribuée en Java

 
Exécutez l'applet et examinez le résultat.

Serait-il possible de déclarer la classe CounterThread différemment, pourquoi ?

Je peux transmettre cela au générateur de threads, pourquoi ?

Modifier le code précédent pour Counter dix fois plus rapide et sans
arrêt.

Exercice 10 : accès concurrent avec des sémaphores

Considérez la classe suivante :

import java.util.concurrent.Semaphore ; la classe Process2 étend Thread { identifiant int privé ; sémaphore privé sem 
Annexe 141 try { sem.acquire(); } catch (InterruptedException e
 

Sémaphore sem = nouveau Sémaphore(4); Process2[] p = nouveau Process2[4] ; for (int i = 0; i < 4; i++) { p[i] = new Pro

}
Des questions:
1) Lancer l'exécution et expliquer la déclaration Semaphore sem =
nouveau Sémaphore(4); c'est-à-dire le point du nombre 4, que signifie 4
ici?
2) Relancer l'exécution en changeant le chiffre 4 en 3, 2, 1 et 0,
respectivement. Expliquez pour chacun.
3) Avec la déclaration Semaphore, sem = new Semaphore(1); mettre
sem.release(); en commentaire. Exécuter et expliquer.

Exercice 11 : Le problème du barbier endormi (producteur/consommateur


avec sémaphore)

Un barbier possède un salon avec un siège de barbier et une salle d'attente


contenant N sièges d'attente.

– S'il n'a pas de clients, le barbier s'assoit sur le siège du barbier.


– Lorsqu'un client arrive :
- Si la salle d'attente est pleine, le client revient plus tard.
- Si le coiffeur dort, il le réveille, s'assied sur le siège du coiffeur et
attend sa coupe de cheveux.

142 Programmation simultanée, temps réel et distribuée en Java

 
- Si le coiffeur est occupé lorsque le client arrive, le client s'assied et
s'endort sur une des chaises N de la salle d'attente, il doit attendre le
siège de barbier à disposition.
– Lorsque le coiffeur a terminé une coupe de cheveux, il quitte le client et
va réveiller un des clients dans la salle d'attente.
– Si la salle d'attente est vide, le barbier se rendort sur son
siège de barbier et attend l'arrivée d'un autre client.
Le but de cet exercice est d'associer un fil au barbier également
comme pour chaque client et programmez une séance de barbier endormi en Java. La
l'utilisation de sémaphores est nécessaire pour garantir l'exclusion mutuelle entre les
fils.

Astuce : Un client est caractérisé par son nom et trois sémaphores :


FreeChairs, Wake, BarberChair.

FreeChairs : gère l'exclusion mutuelle par rapport à l'attente


chambre.

Réveil : à utiliser pour permettre au client de réveiller le barbier si celui-ci est


endormi.

BarberChair : gère l'exclusion mutuelle par rapport au barbier


chaise.

Un barbier est caractérisé par deux sémaphores Wake, BarberChair.

Lors de l'initialisation, assurez-vous que les deux sémaphores - Wake, BarberChair


– sont en commun avec barber et les threads clients.

Exemple d'initialisation :

Barber c = new Barber(Wake, BarberChair);

Client cl = nouveau Client("Client " + i + " :", FreeChairs, Wake,


chaise de barbier);

Dans la classe Semaphore, la méthode tryAcquire() tente d'acquérir un


puce pour accéder à la section critique (renvoie vrai en cas de succès, faux en
cas d'échec).

Annexe 143

 
Exemple : while (!FreeChairs.tryAcquire()) sleep(5000); pourrait être utilisé dans
l'exécution du client afin de se mettre en attente s'il n'y a pas de
chaises.

Exercice 12 : gestion des entrées/sorties d'un parking

Dans cet exercice, nous souhaitons compter les entrées et les sorties des véhicules dans un
parking pour afficher le nombre de places disponibles dans le parking chaque
moment où une voiture sort. Le fonctionnement du parking est simple : il y a un seul
entrée au parking qui a une capacité de N.

Le but de cet exercice est de compter le nombre d'espaces libres dans le


parking en utilisant une seule variable partagée.

Cette variable doit être déclarée dans une classe Car (le thread qui représente
le comportement d'une voiture) comme suit : static int cont=N ;

Une seule classe est nécessaire dans cet exercice (Classe Voiture), et cette dernière est
caractérisé par un nombre entier i et un sémaphore s.

Exercice 13 : dîner des philosophes

Ce problème est un grand classique [DIJ 70]. Cinq philosophes sont réunis pour
effectuer deux activités principales : penser et manger. Chaque philosophe pense pour un
un laps de temps aléatoire, mange (si possible) pendant un laps de temps aléatoire,
puis revient à la réflexion. Quand un philosophe demande à manger, une assiette de
les spaghettis attendent. Les cinq assiettes sont disposées autour d'une table ronde comme
montré dans la figure ci-dessous:
144 Programmation simultanée, temps réel et distribuée en Java

 
Pour qu'un philosophe mange ses spaghettis, il faut deux fourchettes : la sienne (la
celui de droite) et celui de son voisin de gauche. Naturellement, si l'un des
voisins mange déjà, le philosophe ne peut pas manger et doit attendre un
ou les deux fourches pour les libérer.

La solution à ce problème doit être la plus optimisée possible : une


le philosophe voulant mais ne pouvant pas manger doit attendre son tour sans
en consommant inutilement du temps CPU.

Chaque philosophe exécute le cycle un certain nombre de fois : RÉFLÉCHIR, DEMANDER


POUR LA NOURRITURE, MANGEZ.

On ne voit naturellement pas deux philosophes manger côte à côte en même temps
temps.

Un thread peut se voir refuser l'accès à une ressource lors d'une


durée indéterminée. On dit alors que le fil est affamé. UN
le philosophe ne peut pas manger pendant un laps de temps défini lorsqu'il est affamé. Cependant,
il y a situation d'inter-blocage quand tous les philosophes tentent de prendre une
fourche en même temps.

– Programmer une solution en java qui utilise deux classes : Dinner (contient le
"main", etc.) et Philo (thread correspondant à l'activité d'un
philosophe).

Exemple d'exécution :

Philo0 réfléchit
Philo2 réfléchit
Philo1 réfléchit
Philo3 réfléchit
Philo4 réfléchit
Philo4 veut manger
Philo4 mange
Philo0 veut manger
Philo3 veut manger
Philo1 veut manger
Philo1 mange
Philo2 veut manger

Annexe 145

 
Philo4 a fini de manger
Philo3 mange
Philo4 réfléchit
Philo1 a fini de manger
Philo1 réfléchit
Philo0 mange
Philo4 veut manger
Philo1 veut manger
Philo3 a fini de manger
Philo3 réfléchit
Philo2 mange
Philo0 a fini de manger
Philo0 réfléchit
Philo4 mange
...
– Lors de vos tests, assurez-vous qu'il n'y a pas deux philosophes côte à côte.
manger en même temps, et qu'il n'y a pas de famine, et bien sûr, que
il n'y a pas d'interblocage.

Exercice 14 : producteur/consommateur
Considérez les trois classes Java suivantes : B.java, Producer.java et
Consommateur.java.

Le Producteur et le Consommateur partagent un objet b de type B.


classe B {
tampon Objet privé [ ] ;
privé int taille ;
prem int privé, der, nbObj ;
public B (entier t) {
taille = t;
buffer = new Object [taille] ;
prem = 0 ;
der = 0 ;
nbObj = 0 ;
}

146 Programmation simultanée, temps réel et distribuée en Java

 
dépôt de vide synchronisé public (Object obj) {
while (cond1) try {wait ();}catch (InterruptedException e) {}
tampon [der] = obj ;
der = (der + 1) % taille ;
nbObj = nbObj + 1 ;
notifier ();
}
Objet public synchronisé collecté () {
while (cond2) try {wait ();}catch (InterruptedException e) {}
Objet obj = buffer [prem] ;
tampon [prem] = null ;
prem = (prem + 1) % taille ;
nbObj = nbObj - 1 ;
notifier ();
retour (obj);
}
} // fin B

class Producer implémente Runnable {


privé B b;
int val privé;
Producteur public (B b) {this.b = b;}
public void run () {
tandis que (vrai) {
b.putsdown (nouvel entier (val));
System.out.println (Thread.currentThread ().getName () +"a déposé" +
val);
va ++;
essayez {Thread.sleep ((int)( Math.random ()*100));}catch ( Interrompu
Exception e) {}
}
}// fin d'exécution
}// endProducteur

Annexe 147

 
class Consumer implémente Runnable {
privé B b;
Consommateur public (B b) {this.b = b;}
public void run () {
Valeur entière ;
tandis que (vrai) {
val = (Entier) b.preleve();
System.out.println ( Thread.currentThread ().getName () +"collecté" + val );
essayez {Thread.sleep ((int)( Math.random ()*300));}catch ( interrompu
Exception e) {}
}
}// fin d'exécution
}// Consommateur final

– Compléter le code de la classe B en donnant cond1 et cond2.


– Dans la méthode putsdown, quel est le rôle de l'instruction der = (der +
1) % taille ?
– Sur la base de la question précédente, quel est le type de tampon utilisé pour un
objet de type B ?
– Écrivez la classe Primary.java, cette dernière lance deux threads P de type
Producteur et C de type Consommateur. Le nom du fil P doit être Prod
et le nom du thread C doit être Consu. La taille du tampon partagé
est 4.

Exercice 15 : synchronisation des threads

Vous êtes responsable d'un grand projet informatique chargé de piloter


voitures automatiques. Malheureusement, ils ne roulent pas dans le même sens et
la route qu'empruntent ces voitures comporte un tronçon à sens unique. A tout moment, plus
plus d'une voiture (N dans le cas général) peut circuler sur la route à sens unique
condition qu'ils empruntent le même chemin, si au moins une voiture est présente sur
le segment à sens unique, les voitures de l'autre côté devront attendre la route
être disponible.

148 Programmation simultanée, temps réel et distribuée en Java

 
Voici l'opération possible avant votre intervention :
Utilisez la synchronisation des threads pour trier ce problème.

Exercice 16 : factorielle et Fibonacci avec les sockets TCP

Considérez la fonction suivante :

Fonction factorielle (n : int) : entier


Début Si n > 1 Retour n * factoriel(n - 1) Sinon Retour 1 Fin si

Fin

– Implémenter cette fonction côté serveur et créer un client/serveur


modèle utilisant des sockets TCP sous Java ; le client aura un entier n (>=0)
au client, et ce dernier doit répondre par un factoriel (n).
– La suite de Fibonacci est une suite d'entiers dont chaque entrée est la
somme des deux nombres entiers qui le précèdent. Il commence généralement par le
les chiffres 0 et 1 (parfois 1 et 1) et ses premiers chiffres sont : 0, 1, 1, 2, 3,
5, 8, 13, 21, etc. Votre client doit maintenant envoyer un entier n au serveur, et
ce dernier doit répondre par le nième nombre de la suite de Fibonacci. Pour
exemple, si le client envoie 8 au serveur, il en récupère 13 (le huitième
Numéro).

Annexe 149

 
Exercice 17 : mystère avec les sockets TCP

– Quelle est la méthode mystère de la classe Java suivante ?

//// A.java
importer java.io.* ;
public class A{ public static void mystère (InputStream in, OutputStream out) jette

IOException { byte buf[ ] = new byte[1024] ; int n ; while((n=in.read(buf))!=-1) out.write(buf,0,n); joindre(); out.clos

– Considérez les deux classes Java suivantes :

/////////// Client.java
importer java.io.* ;
importer java.net.* ;
public class Client{ public static void main(String []args) throws IOException { Socket sock = new Socket(InetAddre
}

//// Serveur.java
importer java.io.* ;
importer java.net.* ;

150 Programmation simultanée, temps réel et distribuée en Java

 
public class Serveur{ public static void main(String []args) throws IOException { Socket sock = new ServerSocket(9

– Que fait cette application en exécutant les deux classes (Client et


Serveur) dans localhost ?
– En utilisant deux machines différentes (une machine côté client
et une autre côté serveur), quel sera l'objectif de cette
application?

Exercice 18 : gérer un compteur avec les sockets TCP

Nous avons une liste de guichets bancaires. Un compteur est caractérisé par son compte
titulaire, son numéro et son solde.

Le client se connecte au serveur et envoie un entier M, le serveur doit


répondre avec la liste des titulaires de compte guichet qui ont un solde plus élevé
que M

– Ecrire les deux classes ClientCounter et ServeurCounter selon


le modèle de sockets TCP en java.
– Maintenant, votre client doit se connecter au serveur en envoyant un compteur
numéro afin de récupérer toutes les informations sur ce compteur (sous forme de
un objet pour une première version et la forme d'une chaîne de caractères pour une seconde
version). Apportez les modifications nécessaires à votre code afin de répondre
cette question.

Annexe 151

 
Exercice 19 : applet et sockets TCP
Créez le modèle client/serveur suivant :

Pour une version couleur de la figure, voir www.iste.co.uk/benmammar/java.zip

Améliorer l'IHM côté client et le traitement côté serveur


pour effectuer des opérations bancaires classiques :

– Ouvrir un nouveau Comptoir ;


- Ajouter de l'argent à un compteur avec un numéro connu ;
– Retrait à un Guichet dont le numéro est connu ;
– Afficher le nom du titulaire du compte et le solde d'un Compteur
avec un numéro connu ;
– Afficher le numéro du compteur lorsque le nom du titulaire du compte est
connu;
– Afficher la somme moyenne des soldes de tous les Compteurs ;
– Afficher la liste des compteurs.

152 Programmation simultanée, temps réel et distribuée en Java

 
Exercice 20 : opérations arithmétiques avec des sockets TCP

Dans cet exercice, ADD, MUL, DIV et SUB correspondent respectivement à


opérations d'addition, de multiplication, de division et de soustraction.

Votre objectif est de créer une application client/serveur utilisant des sockets TCP.
Le client envoie deux opérandes au serveur ainsi que l'opération à
effectuer (tous les trois sous la forme d'une seule chaîne de caractères), et le serveur
 
répond avec le résultat de l'opération. Par exemple, si le client devait
envoyer au serveur :
– ADD 6 1, le serveur répondrait par 7.
– MUL 10 88, ce dernier répondrait par 880.
– SOUS 5 9, le serveur répondrait par -4.
– DIV 9 8, le serveur répondrait par 1.125. (division réelle).
– PARIS 6 7, le serveur répondrait par hhhhh erreur.

Exercice 21 : tables de multiplication avec sockets TCP

– Créer un modèle client/serveur à l'aide de sockets TCP java ; le client envoie un


entier au serveur (4 par exemple), et ce dernier répond par le
table de multiplication de l'entier.
Le client devra par exemple afficher :
Table de multiplication de 4 :

0 4 8 12 16 20 24 28 32 36 40

– Votre client doit maintenant envoyer un tableau d'entiers, en disant {1, 6, 8, 9, 13,
dix}.
– Le serveur doit répondre par la sous-table ne contenant que des
entiers ({6, 8, 10}).

Exercice 22 : manipulations de bas niveau des sockets TCP

Utilisation de manipulations de socket TCP de bas niveau (écriture de OutputStream et


lire InputStream), écrire un modèle client/serveur dans lequel le client envoie le
serveur une phrase contenant deux mots (ex. PARIS MADRID).

Annexe 153

 
Le serveur se propagera via la même phrase mais sur deux lignes (chacune
mot sur une ligne).

Dans cet exemple, le serveur répondra par :

PARIS
MADRID

Exercice 23 : manipuler des chaînes de caractères avec RMI

– En utilisant Java RMI, écrivez une méthode distante pour calculer l'inverse d'un
mot. Pour cela, nous devons écrire l'interface partagée entre le client et
le serveur, l'implémentation client et l'implémentation serveur.
– Ajouter une deuxième méthode distante pour retourner la sous-chaîne contenant le
quatre caractères au début de ce mot.
– Ajouter une troisième méthode à distance pour savoir si le mot est un
palindrôme.

Exercice 24 : Fibonacci avec RMI

La suite de Fibonacci est une suite d'entiers dans laquelle chaque nombre est
la somme des deux nombres qui la précèdent. Il commence généralement par 1 et son premier
les nombres sont : 1, 1, 2, 3, 5, 8, 13, 21, etc.

À l'aide de Java RMI, écrivez une méthode distante qui a un paramètre de nombre n
et qui renvoie les n premiers entiers de la suite. Envoi 5, par exemple,
au serveur, ce dernier répondra par la chaîne de caractères suivante : 1,
1, 2, 3, 5.

Exercice 25 : noeud papillon avec RMI

En utilisant Java RMI, écrivez une méthode distante qui reçoit une chaîne de
caractères avec un nombre impair de caractères (vous devrez effectuer un test
sur la longueur du mot) et l'affiche sous la forme d'un nœud papillon. Pour
Exemple:
ho
il lo
154 Programmation simultanée, temps réel et distribuée en Java

 
bonjour
il lo
ho

Exercice 26 : service de registre avec RMI

Nous souhaitons mettre en place un service de registre qui enregistre les noms et numéros de téléphone
numéros et permet à l'utilisateur d'y accéder à l'aide d'un nom. Le registre doit
être accessible à distance via RMI. Nous considérons que le nom et le numéro sont
chaînes de caractères.
– Définir une interface RMI répondant à ces spécifications.
– Développer un objet serveur implantant cette interface.
– Développer un client qui interroge l'objet serveur précité.

Bibliographie

[AIC 17] AICAS, disponible sur : http://www.aicas.com/, 2017.


[DIJ 71] DIJKSTRA EW, « Ordonnancement hiérarchique des processus séquentiels » dans B RINCH
H ANSEN P. (ed.), L'origine de la programmation simultanée, Springer, New York,
1971.
[ORA 17a] ORACLE , "Class Thread", disponible sur : https://docs.oracle.com/javase/7/
docs/api/java/lang/Thread.html, 2017.
[ORA 17b] O RACLE , "Class ThreadGroup" disponible sur : https://docs.oracle.com/
javase/7/docs/api/java/lang/ThreadGroup.html, 2017.
[JAV 17a] J AVA ® C OMMUNITY P ROCESS , « Real-Time Specification for Java »,
disponible sur : http://www.rtsj.org/, 2017.
[JAV 17b] J AVA ® C OMMUNITY P ROCESS , « Package javax.realtime », disponible sur :
http://www.rtsj.org/specjavadoc/javax/realtime/package-summary.html, 2017.
[STA 88] S TANKOVIC JA, « Idées fausses sur l'informatique en temps réel : une grave
problem for next-generation systems », Computer, vol.21, no.10, pp.10–19, 1988.
[TAN 01] T ANENBAUM AS, "Systèmes d'exploitation multimédia" en moderne
Operating Systems, 2e éd., Prentice-Hall, Upper Saddle River, 2001.
[TIM 17] TIMESYS, disponible sur : http://www.timesys.com/, 2017.
[UCI 17] U NIVERSITÉ DE C ALIFORNIE , « Irvine », disponible sur : http://www.uci.edu/,
2017.

Indice

A, C, D JNI, 68 ans
JRMP, 96
acquérir, 57, 58, 61, 64
JSSE, 117
API, 91, 117
KVM, 65
asynchrone, 66, 120
Linux, 67 ans
CNI, 68 ans
bas niveau, 72
CORBA, 91, 96
DCOM, 91
M, N, O
détruire, 15
DGC, 125 mobile, 65
Nortel, 66 ans
G, H, je notifier, 47–49, 55–58, 65, 113, 114
nombre, 17, 66, 71–75, 81–83, 89,
GC, 65, 67, 69 96, 106, 107, 125
CCG, 69 OSI, 72
GCJ, 69 ans
GNU, 69 ans
Q, R, S
table de hachage, 106, 108
IIOP, 96 QNX, 66 ans
PI, 72–75, 95, 96 temps réel, 61–63, 65–67, 69
libération, 57, 58
J, K, L À distance, 90, 95–98, 101, 102, 104,
105, 107, 110, 113, 115, 120, 122,
J2EE, 65 ans
123, 125
J2ME, 65 ans
CRIM, 95
J2SE, 65, 72, 95, 117
RPC, 90, 91
JDK, 91, 125
RRL, 95
RTSJ, 65, 66 T, V, O
courir, 3–11, 13, 16, 18, 19, 22–24, 66,
TCP, 72, 73, 77, 89, 95, 96
122
Minuterie, 21–24
sécurisé, 117
variable, 1, 30, 33, 34, 36, 43, 45, 55
sémaphore, 54, 55, 58, 59
attendre, 10, 13, 38, 45, 47–49, 55, 56,
douille, 72–77, 79–83, 87–89, 116,
58, 63, 64, 73, 81
117
WinCE, 62 ans
SSL, 117

Vous aimerez peut-être aussi