Vous êtes sur la page 1sur 44

République Algérienne Démocratique et Populaire.

Ministère de l’Enseignement Supérieur et de la Recherche Scientifique.


Université des Sciences et de la Technologie d’Oran –USTO Mohamed Boudiaf.
Faculté des Sciences – Département d’Informatique.

Post-graduation
Reconnaissance des Formes & Intelligence Artificielle.

Exposé :

Langages de Programmation Parallèle,


Java & Simula.

Module : Parallélisme.
Responsable : M. BELKADI.

Présenté par : Abderrezak BRAHMI


Ahmed GUEDDECHE

Septembre 2003
Langages de Programmation Parallèle, Java & Simula.

Sommaire

SOMMAIRE.......................................................................................................................................1
INTRODUCTION..............................................................................................................................2
PARTIE I : JAVA ..............................................................................................................................3
1- INTRODUCTION .............................................................................................................................3
2- CONCEPTS ET NOTIONS DE BASE....................................................................................................3
3- APPLETS ET PROGRAMMES JAVA ...................................................................................................9
4. LES FICHIERS ...............................................................................................................................13
5. JAVA EN RESEAU .........................................................................................................................15
6. PARALLELISME SOUS JAVA..........................................................................................................18
PARTIE II : SIMULA .....................................................................................................................28
1- INTRODUCTION ...........................................................................................................................28
2. DESCRIPTION GENERALE .............................................................................................................28
3. CALCUL ARITHMETIQUE ..............................................................................................................30
4. AUTRES DECLARATIONS ..............................................................................................................33
5. PROCEDURES ...............................................................................................................................35
6. ASPECTS AVANCES ......................................................................................................................36
7. ASPECT PARALLELE.....................................................................................................................40
CONCLUSION ................................................................................................................................43
BIBLIOGRAPHIE...........................................................................................................................43

1
Langages de Programmation Parallèle, Java & Simula.

Introduction
Il est assez clair aujourd’hui que la maîtrise de la construction et de l’évolutivité d’un grand système
réparti passe par l’adoption d’un processus de conception unifié, permettant de traverser en douceur
les différentes phases du cycle de développement. Les langages de programmation orientés-objets
(LOO) ne constituent qu’un outil hôte de codage que l’on doit bien maîtriser avant de pouvoir
efficacement l’utiliser. Certes ces langages sans très bien adaptés à la programmation parallèle,
reste à maîtriser tout le processus de développement d’un produit utile, efficace et réutilisable.
Pour relever le défi d’un développement continuellement validé des systèmes répartis ouverts
fabriqués à base d’objets, il faut particulièrement soigner :
• la prise en compte correcte du parallélisme et de la répartition dans une approche objets, sans
trop nuire aux performances des implantations,
• la disponibilité d’outils de validation dans toutes les phases du cycle de développement.
Dans la perspective de maîtriser les outils de « parallélisation » offerts par les LOO, nous
présentons dans cet exposé deux langages repères dans le domaine. Simula, l’ancêtre des LOO, et
l’un de ses derniers petits-fils, Java.
Nous consacrons pour chacun des deux une partie séparée présentant les concepts de base, quelques
aspects avancés, plusieurs exemples, mais surtout le mécanisme et les outils de parallélisation.

2
Langages de Programmation Parallèle, Java & Simula.

Partie I : JAVA

1- Introduction
Les grandes sociétés, ont adopté Java bien plus intensément qu’on le pense. Chez IBM, la moitié
des salariés s’emploient à recoder en Java des milliards de lignes de programmes. L’autre moitié
s’efforce de faire efficacement tourner Java sur toutes les plates-formes [4].
Le langage de programmation Java a été développé en décembre 1990 par « Sun Microsystems »
dans le cadre du projet « Green ». Les chercheurs –spécialisés dans l’électronique grand public-
travaillaient sur un langage de programmation, qui permettrait aux futurs ustensiles domestiques de
communiquer entre eux. L’idée de départ était de développer le système d’exploitation Star7 en
C++, mais James Gosling, membre du projet Green, fut vite lassé du peu d’efficacité de C++ pour
cette tâche précise ; enfermé dans son bureau, il écrivit alors un nouveau langage permettant de
mieux gérer le Star7.
Java n’étant pas retenu comme outils de développement pour ustensiles, l’avenir du projet Green
sembla donc compromis. C’est alors que le World Wide Web prit son essor.
Vu ses caractéristiques applicables au Web (non encombrant, portable, sécurisé), Java a été
considéré comme un jouet créatif pour les utilisateurs du Web, lors de sa mise en service en 1995
par Sun Microsystems [4].
Les langages de programmation sont en général interprétés (Basic ou JavaScript) ou compilés
(Pascal ou C). Pour pouvoir être un langage multi-plateforme, Java est un mélange de ces deux
possibilités : les fichiers sources .java doivent être compilés pour fournir un fichier .class qui pourra
lui, être interprété. Programmer en java suppose donc que l'on dispose d'un compilateur et d'un
interpréteur java. Ceux-ci sont fournis par le JDK (Kit de Développement en Java) disponible
gratuitement sur le site de la société SUN [2][4].
2- Concepts et notions de base
Lire et apprendre toute la syntaxe d’un langage avant de passer à des pratiques pilotes est une
approche peu rentable pour découvrir et maîtriser un langage. Un programmeur « assoiffé » de
codage préfère apprendre quelques concepts et pratiquer de petits programmes afin de chercher
toute la fermeture syntaxique du langage. Java puise l’essentiel de sa syntaxe du langage C ou
mieux encore C++, il exploite largement les concepts orientés-objets.
2.1. Syntaxe générale
Sans nous bloquer dans une labyrinthe syntaxique, nous donnons brièvement la syntaxe globale
d’une déclaration d’une classe Java. Les autres règles syntaxiques seront découvertes
progressivement à travers la panoplie d’exemples présentés par la suite.
class_declaration
::=
{ modifier } "class" identifier
[ "extends" class_name ]
[ "implements" interface_name { "," interface_name } ]
"{" { field_declaration } "}"

3
Langages de Programmation Parallèle, Java & Simula.

La déclaration d'une interface est similaire à la déclaration d'une classe, sauf que l'on ne fait que
déclarer les méthodes, sans les remplir.

Nous expliquons surtout la notions de modificateur (règle de visibilité):


modifier
::=
"public"
| "private"
| "protected"
| "static"
| "final"
| "native"
| "synchronized"
| "abstract"
| "threadsafe"
| "transient"

Attributs de visibilité des variables et des méthodes


public
Une variable, méthode ou classe déclarée public sera visible par toutes les autres méthodes, à l'intérieur ou à l'extérieur de
l'objet. Généralement, on évite de classer des variables comme public et on crée des méthodes spéciales pour les mettre à
jour. Cela permet un meilleur contrôle et un code plus sûr.
protected
Ce modificateur définit l'accès comme suit : si dans une classe, on déclare une méthode ou une variable comme protected,
seules les méthodes présentes dans le même paquetage que cette classe ou ses sous-classes pourront y accéder. On ne peut
pas utiliser le modificateur protected pour qualifier une classe.
Par défaut, package, friendly
Il n'existe pas de mot-clé pour définir ce niveau de visibilité, c'est le niveau de visibilité par défaut, lorsque qu'aucun modifier
n'est précisé. Lorsqu'une entité (classe, méthode, variable) est déclarée comme cela, elle est visible par toutes les classes se
trouvant dans le même paquetage.
private protected
Une méthode ou une variable private protected n'est visible que dans sa classe ou dans ses sous-classes. Une classe ne
peut pas être private protected.
private
Il s'agit du niveau de restriction de visibilité le plus fort. Seules les méthodes de la classe dans laquelle on définit une entité
private pourront la voir.
static
Une variable déclarée static est partagée entre toutes les instances d'une même classe. On appelle une telle variable une
variable de classe. Une méthode statique (static) est une méthode qui n'agit pas sur des variables d'instance, mais uniquement
sur des variables de classe. On parle de méthode de classe.
final
Lorsque le modificateur final est ajouté à une classe, cela signifie que la classe ne peut pas être sous-classée.
Une variable qualifiée de final signifie que la variable est constante. Attention, cela ne signifie pas que le compilateur empêche
ou contrôle les éventuelles modifications ! Remarquons également que l'on ne peut pas déclarer de constante locale à une
méthode.
Une méthode finale ne peut pas être remplacée dans une sous-classe. On peut aussi remarquer qu'une méthode possédant le
modificateur final pourra etre optimisée par le compilateur, car elle ne sera pas sous-classée.
native
Une méthode native est une méthode qui est implémentée dans un autre langage. L'utilisation de ce type de méthode limite
donc la portabilité du code, puisqu'il faudra recompiler les méthodes natives sur les autres plateformes.
synchronized
ce modificateur sera expliqué plus loin qu’on on discutera l’exclusion mutuelle.
volatile
4
Langages de Programmation Parallèle, Java & Simula.

volatile précise que la variable peut être changée par un périphérique ou de manière asynchrone. Cela indique au compilateur
qu'il ne doit pas stocker les valeurs dans des registres. A chaque utilisation, on lit la valeur, et on réécrit immédiatement le
résultat s'il a changé.
abstract
Le modificateur abstract indique que la classe auquel on l'ajoute est une classe qui ne pourra pas etre instanciée telle quelle.
De plus, toutes les méthodes déclarées abstract dans cette classe ne sont pas implémentées et devront être remplacées par
des méthodes complètes dans ses sous-classes.
transient
Ce mot clé a été réservé par Sun, et ne doit pas être utilisé comme identificateur. Il sera vraisemblablement utilisé par le futur
système de distribution d'objet. La page http://www.javasoft.com/ vous tiendra au courant des nouveaux développements.

2.2. Introduction aux classes et aux objets


Une classe est un type d'objet. On peut comparer la description d'une classe à celle d'un article
(record), contenant à la fois des données et des méthodes nécessaires au traitement de celles-ci. On
peut par exemple imaginer une classe Voiture qui contiendrait des données - immatriculation, type -
ainsi que des procédures - localiser(). Ces procédures sont appelées méthodes en programmation
orientée-objets, et elles opèrent sur des variables contenues dans l'objet.
Nous l'avons dit précédemment, une classe est en quelque sorte un type complexe. Lorsque l'on crée
un objet d'une certaine classe, on parle d'une instance de cette classe. Pour créer un objet, on fait
appel à un de ses constructeurs.
Voyons comment créer une instance voiturePatron de la classe Voiture, et comment la "localiser".
Voiture voiturePatron = new Voiture("patron","GE999",0,0);
position = voiturePatron.localiser();
La première ligne crée une nouvelle instance de la classe Voiture, en fournissant 4 paramètres au
constructeur. En deuxième ligne, nous faisons un appel à la méthode localiser() de l'instance
voiturePatron. Le point marque la séparation entre le nom de l'objet et le nom de la méthode de cet
objet. Si l'on veut faire référence à une variable contenue dans cet objet, on utilisera la même
notation.
Dans cet exemple, localiser() ne prend pas de paramètre, mais il est tout de même requis de postfixer
le nom de la méthode avec des parenthèses vides.
2.3. Héritage
L'avantage d'un langage orienté-objets est que le code est réutilisable. Grâce à l'héritage, on peut
modifier une classe sans avoir à la réécrire complètement. On dit que la classe nouvelle hérite de la
classe ancienne. On dit aussi que nouvelle étend ancienne ou que nouvelle est dérivée de ancienne. nouvelle
est alors une sous-classe de ancienne, et ancienne une super-classe (surclasse) de nouvelle.
Dans la pratique, cela signifie qu'une instance de nouvelle aura la possibilité d'utiliser des méthodes
définies dans ancienne. Lorsque l'on code la classe nouvelle, on a la possibilité de remplacer des
méthodes de la super-classe (classe parente), simplement en définissant une méthode avec le même
nom et les mêmes paramètres. On remarquera qu'il n'est pas possible de remplacer un constructeur,
puisque celui-ci porte le nom de la classe dans laquelle il est déclaré, et que le nom des deux classes
diffère obligatoirement.
En Java, toutes les classes sont dérivées de la classe spéciale Object. C'est la racine de la hiérarchie
des classes. Cela veut dire que toutes les classes Java possèdent un certain nombre de méthodes. La
documentation des API disponible chez Sun les explique.
Ci-dessous, vous trouverez la classe Crayon, et la classe CrayonCouleur qui l'étend.

Crayon
class Crayon {
protected int longueur = 100; // en millimetres
protected int diametre = 5; // en millimetres

// cree un crayon standard


public Crayon() {
}

5
Langages de Programmation Parallèle, Java & Simula.
// cree un crayon de longueur l et de diametre d
public Crayon(int l,int d) {
longueur = l;
diametre = d;
}

public int quelleLongueur() {


return longueur;
}

public int quelDiametre() {


return diametre;
}

public void changeLongueur(int nouvelleLongueur) {


longueur = nouvelleLongueur;
}

}
CrayonCouleur
class CrayonCouleur extends Crayon {

protected String couleur = "gris";

public CrayonCouleur() {
super();
}

public CrayonCouleur(int l,int d,String c) {


super(l,d); // initialise les variables longueur
// et diametre en utilisant le
// constructeur de la "superclass"
couleur = c;
}

public CrayonCouleur(String c) {
super();
couleur = c;
}

public String quelleCouleur() {


return couleur;
}

public void changeCouleur(String nouvelleCouleur) {


couleur = nouvelleCouleur;
}
}

2.4. Surcharge de méthodes


La surcharge (overloading) permet au programmeur de déclarer plusieurs méthodes ou
constructeurs ayant le même nom, mais ayant des paramètres différents. Au moment de la
génération de code, le compilateur identifie tous les appels à ces méthodes. Il les compare avec les
déclarations des méthodes, et choisi la méthode à appeler en fonction de ses paramètres.
...
int somme(int a,int b) {
return (a+b);
}

int somme(int a,int b,int c) {


return (a+b+c);
}

float somme(float a,float b) {


return (a+b);
}
...

6
Langages de Programmation Parallèle, Java & Simula.
...
int entA = 1,entB = 2,entC = 9;
float fA = 1.3,fB = 2.6;

int entSomme = somme(entA,entB,entC);


float fSomme = somme(fA,fB);
...
println est une méthode de la classe PrintStream qui accepte plusieurs types de paramètres différents,
grâce à la surcharge. Le paramètre de println est calculé avant l'appel à la méthode. Ainsi, println(1+1)
affichera 2 alors que println(1+" "+1) affichera 1 1.

2.5. Interfaces
Une interface est un modèle d'implémentation. Tous les objets qui implémentent une interface
possèdent les méthodes déclarées dans celle-ci. On utilise souvent les interfaces dans les types de
paramètres. En effet, dans certains cas, peu importe la classe d'un objet donné, car cela ne nous
intéresse pas. Nous avons seulement besoin de savoir que telle ou telle classe possède ces méthodes
et se comporte de telle manière.
On peut imaginer une interface appareilElectrique qui définit les méthodes estEnclenche() et
alimente(boolean). Si une classe implémente l'interface appareilElectrique, on sait donc qu'elle possède au
moins ces deux méthodes, qu'il s'agisse d'une radio, d'une lampe ou d'un autre appareil. Lorsque l'on
désire indiquer à un appareil qu'il est alimenté, on exécute app.alimente(true), et pour lui indiquer qu'il
a été débranché, on exécute app.alimente(false).
Nous allons donc créer deux types d'appareils électriques, les radios et les lampes. Ces deux types
implémentent l'interface appareilElectrique. Nous allons également créer une classe rallongeElectrique,
qui est aussi un appareil électrique. La méthode la plus intéressante de cette classe est la méthode
branche(appareilElectrique). Celle-ci enregistre l'objet fourni en paramètre comme étant l'objet alimenté
à travers la rallonge. Cette rallonge possède un interrupteur, que l'on peut activer ou désactiver en
appelant ses méthodes enclenche(), respectivement declenche(). Lorsque l'on change l'état de
l'interrupteur, on indiquera à l'objet qui en dépend qu'il est ou qu'il n'est plus alimenté. La page
"déclaration d'interface" peut également intéresser le lecteur.
Interface - appareilElectrique
public interface appareilElectrique {

/** teste si l'appareil est enclenche


*/
public boolean estEnclenche();

/** on appelle cette methode lorsque l'on


* branche l'appareil dans une source de
* courant active, avec true, ou false
* si la source est inactive
*/
public void alimente(boolean alim);

}
Implémentation - radio
// c'est une radio rudimentaire, qui se deregle quand on l'eteint !

class radio implements appareilElectrique {

final static int freqMiseEnRoute = 1007; // 100.7 MHz


int freq;
boolean allumee = false;

public boolean estEnclenche() {


return allumee;
}

public void alimente(boolean a) {


allumee = a;
if (allumee)

7
Langages de Programmation Parallèle, Java & Simula.
freq = freqMiseEnRoute;
}

/** on ne peut changer de frequence que si la


* radio est en marche
* retourne true si le changement a ete effectue
*/
public boolean changeFreq(int freq) {
if (!allumee)
this.freq = freq;
return allumee;
}

}
Implémentation - lampe
class lampe implements appareilElectrique {

boolean allumee = false;

public boolean estEnclenche() {


return allumee;
}

public void alimente(boolean alim) {


allumee = alim;
}

}
Implémentation - rallongeElectrique
class rallongeElectrique implements appareilElectrique {

appareilElectrique appareilBranche = null;


boolean conduit = false; // indique si l'interrupteur est ferme
(conduit=true)
boolean estAlimente = false;

public boolean estEnclenche() {


return conduit;
}

public void alimente(boolean alim) {


if (alim) { // on branche la rallonge dans une prise
if (!estAlimente && conduit)
on();
} else // on debranche
if (estAlimente && conduit)
off();
estAlimente = alim;
}

/* gestion de l'interrupteur
*/
public void enclenche() {
if (!conduit && estAlimente)
on();
conduit = true;
}

public void declenche() {


if (conduit && estAlimente)
off();
conduit = false;
}

/* gestion de l'appareil branche


*/
private void on() {
if (appareilBranche != null)

8
Langages de Programmation Parallèle, Java & Simula.
appareilBranche.alimente(true);
}

private void off() {


if (appareilBranche != null)
appareilBranche.alimente(false);
}

// branche un nouvel appareil a la rallonge


public boolean branche(appareilElectrique app) {
if (appareilBranche != null) // il y a deja un appareil branche !
return false;
appareilBranche = app;
return true;
}

}
Exemple d'utilisation - testElectrique
class testElectrique {

public static void main(String args[]) {

rallongeElectrique rallonge1, rallonge2;


rallonge1 = new rallongeElectrique();
rallonge2 = new rallongeElectrique();

radio radioSalon = new radio();


lampe lampeCuisine = new lampe();

// on imagine que l'on branche les rallonges dans 2 prises


rallonge1.alimente(true);
rallonge2.alimente(true);

// on branche des appareils dans ces 2 rallonges


rallonge1.branche(radioSalon);
rallonge2.branche(lampeCuisine);

System.out.println("la radio du salon est "+


(radioSalon.estEnclenche() ? "allumee" : "eteinte"));

System.out.println("on appuie sur l'interrupteur de la rallonge 1");


rallonge1.enclenche();

System.out.println("la radio du salon est maintenant "+


(radioSalon.estEnclenche() ? "allumee" : "eteinte"));

}
Exécution
la radio du salon est eteinte
on appuie sur l'interrupteur de la rallonge 1
la radio du salon est maintenant allumee

3- Applets et programmes Java


3.1. Les Applications Java
Une application Java est un programme qui réside sur la machine qui l'exécute. En Java, tout
programme est une classe, et possède donc des méthodes. Lorsque la machine virtuelle Java veut
démarrer le programme, elle appelle la méthode main(String args[]) de l'application chargée. Il faudra
donc placer le code à exécuter dans cette méthode. Le tableau args contient les éventuels paramètres
passés à l'application sur la ligne de commande. Pour plus d'explication sur la déclaration de la
méthode main dans l'exemple ci-dessous, veuillez consulter la page Les modificateurs.

9
Langages de Programmation Parallèle, Java & Simula.

Exemple
class exSimple1 {
public static void main(String args[]) {
int entA = 10;
int entB = 12;
int entX;

System.out.println("L'entier A vaut "+entA);


System.out.println("L'entier B vaut "+entB);

if (entA < entB)


System.out.println("A est plus petit que B");
else if (entA == entB)
System.out.println("A est egal a B");
else
System.out.println("A est plus grand que B");

System.out.println("comptons de 1 a "+entA);

int somme = 0;
int fact = 1;
for (int i = 1;i <= entA;i++) {
System.out.print(" "+i);
somme += i;
fact *= i;
}
System.out.println();
System.out.println("la somme de tous les nombres de 1 a "+entA+" vaut
"+somme);
System.out.println("la factorielle de "+entA+" vaut "+fact);
}
}
Exécution
L'entier A vaut 10
L'entier B vaut 12
A est plus petit que B
comptons de 1 a 10
1 2 3 4 5 6 7 8 9 10
la somme de tous les nombres de 1 a 10 vaut 55
la factorielle de 10 vaut 3628800

3.2. Les Applets Java


Une applet Java est un programme qui est exécuté dans un browser tel que Netscape Navigator, ou
HotJava de Sun. Une applet est intégrée dans une page au format HTML et est automatiquement
téléchargée sur le poste client. Elle est ensuite exécutée par celui-ci.
Le téléchargement transparent et l'exécution automatique posent des problèmes de sécurité, et c'est
pour cela que les applets Java sont limitées dans certains domaines. Typiquement, une applet ne
peut pas gérer de fichiers, ni ouvrir des connections réseau arbitraires.

Intégration de l'applet dans une page Web


Il existe des balises (tags) HTML spécifiques pour indiquer au browser qu'une applet est présente
dans une page. Il s'agit de <APPLET>. Voici un exemple classique :
<HTML>
<HEAD>
<TITLE>Exemple de page avec une applet</TITLE>
</HEAD>
<BODY>
<H1>Exemple d'applet</H1>
<applet code="appletExemple.class" width=100 height=50>
Votre browser ne supporte pas les applets !
</applet>
</BODY>
</HTML>

10
Langages de Programmation Parallèle, Java & Simula.

Mise en oeuvre
Tout programme Java est une classe. Une applet n'échappe pas à cette règle. Si l'on veut créer une
applet, on doit étendre la classe java.applet.Applet. Cette classe contient les méthodes nécessaires à la
gestion de l'applet, et à l'interaction de celle-ci avec son environnement (browser).
Voyons les méthodes les plus importantes, que votre applet devra remplacer si nécessaire :
public void init()
Le browser fait appel à cette méthode lorsque l'applet est chargée (ou rechargée). Cette
méthode devra charger les informations telles que images ou sons, et récupérer les
paramètres présents dans la page HTML.
public void start()
Après avoir été initialisée, l'applet est démarrée, grâce à la méthode start(). L'applet est
également redémarrée après avoir été stoppée, lorsqu'elle est à nouveau visible.
public void stop()
Cette méthode permet à l'applet de s'arrêter lorsqu'elle n'est plus visible, parce que
l'utilisateur a changé de page, par exemple.
public void destroy()
L'applet est détruite lorsque le browser s'arrête, ou avant que l'applet soit rechargée. Cette
méthode doit être remplacée si l’on veut stopper des threads créés par start() de l'applet.
public void paint(Graphics g)
Cette méthode est appelée chaque fois que l'on doit redessiner l'applet. Le paramètre est de
type Graphics et c'est la surface de dessin sur laquelle on doit travailler.
Exemple
import java.awt.*;
import java.applet.*;

public class applet1 extends Applet {

Font font;

public void init() {


font = new Font("TimesRoman",Font.PLAIN,20);
}

public void paint(Graphics g) {


g.setFont(font);
g.setColor(Color.red);
g.drawString("Je suis une applet !",0,font.getSize());
}
}

Cette applet n'implémente pas la méthode start(), car aucune tâche n'est effectuée en permanence. La
seule chose que l'applet fait est de se redessiner lorsque le browser le redemande
(paint(Graphics g)).
Que se passe-t-il si l'on veut que l'applet ait une activité permanente ? Toutes les applets s'exécutant
dans browser utilisent la même machine virtuelle Java. On ne peut donc pas créer une boucle dans
la méthode start() pour effectuer une tâche, parce que l'on occuperait la MV (machine virtuelle). Il
faut donc créer un thread afin d'autoriser l'exécution parallèle des multiples applets.
Exemple
import java.awt.*;

public class applet2 extends java.applet.Applet implements Runnable {

Thread tache;
String lbl;
Font font;
boolean gris = false;

public void init() {


font = new java.awt.Font("TimesRoman", Font.PLAIN, 24);
lbl = getParameter("lbl");

11
Langages de Programmation Parallèle, Java & Simula.
}

public void paint(Graphics g) {


g.setFont(font);
if (gris)
g.setColor(Color.lightGray);
else
g.setColor(Color.black);
g.drawString(lbl, 0, font.getSize());
}

public void start() {


tache = new Thread(this);
tache.start();
}

public void stop() {


tache.stop();
}

public void run() {


while (true) {
try {
Thread.sleep(1000); // pause de 1 seconde
} catch (InterruptedException e) {}
gris = !gris;
repaint(); // on force le réaffichage
}
}
}

3.3. Gérer les événements d’une applet


Le logiciel de navigation envoie des événements à l'applet par exemple lorsque l'utilisateur presse
une touche du clavier ou utilise sa souris. Il envoie ces événements à l'applet en appelant une des
méthodes prévues à cet effet. On peut détecter ces événements en remplaçant les méthodes keyDown
ou mouseDown.
Cet exemple fait une utilisation très simple des événements qu'il reçoit. S'il s'agit d'un click de
souris, on incrémente le compteur c. Si l'utilisateur a pressé une touche flèchée vers le haut ou vers
le bas, on augmente ou diminue le compteur. On peut le remettre à zéro en pressant 0.
Exemple
import java.awt.*;
import java.applet.*;

public class applet3 extends Applet {

int c = 0; // valeur du compteur


Font font; // police de caracteres

public void init() {


// on choisit la plus grande police possible : la hauteur de l'applet
font = new Font("Helvetica",Font.BOLD,size().height);
}

public void paint(Graphics g) {


g.setFont(font);
g.setColor(Color.blue);
g.drawString("c = "+c,0,font.getSize());
}

public boolean mouseDown(Event evt,int x,int y) {


c++;
repaint();
return true;
}

public boolean keyDown(Event evt,int key) {


if (key == Event.UP)
12
Langages de Programmation Parallèle, Java & Simula.
c++;
else if (key == Event.DOWN)
c--;
else if ((char)key == '0')
c = 0;
repaint();
return true;
}
}

3.4. Le fenêtrage avec AWT


La librairie AWT (AWT Toolkit) fournit au programmeur toutes les primitives nécessaires à la
gestion de l'interface graphique utilisateur. Cette librairie utilise le système graphique sous-jacent
pour afficher des objets graphiques tels que boutons, fenêtres, etc. Malgré tout, toute applet ou
application fonctionnera indépendamment du système d'exploitation. Pour les premiers exemples
graphiques, vous pouvez consulter les pages sur les applets et sur les événements AWT.

Il existe principalement quatre types d'objets dans le monde AWT.


Les conteneurs (container)
Les conteneurs sont capables de contenir des canevas, des composants graphiques, ou
d'autres conteneurs. Le panneau, décrit plus loin, est un exemple typique de conteneur.
Les canevas (canvas)
Un canevas est un objet graphique très simple, sur lequel on ne peut que dessiner et afficher
des images. On parle aussi de toile de fond, ou tout simplement de fond.
Les composants graphiques (graphic components)
Les composants graphiques regroupent les objets traditionnels présents dans une interface
graphique. On retrouve par exemple les boutons, les listes, les champs de saisie de texte, et
bien d'autres objets encore.
Les composants de fenêtres (windowing components)
Les composants de fenêtres permettent de créer des fenêtres ou des boîtes de dialogue. La
gestion des menus et de la barre de titre font également partie de ce type d'objets.
Généralement, une applet ne fait pas usage de ces objets, bien que cela soit possible.

Lorsque l'utilisateur agit sur un objet graphique tel que bouton ou champ de texte, le logiciel de
navigation envoie des actions et des événements au panneau contenant l'objet. Les actions sont
similaires aux événements, sauf qu'ils ne sont pas traités par la méthode handleEvent, mais action.
Chaque panneau possède sa propre méthode action. Si l'on ne remplace pas cette méthode par une
méthode spécifique, l'événement (action) se propagera automatiquement au panneau parent, et ainsi
de suite.

4. Les fichiers
En Java, on peut facilement accéder à des fichiers à partir d'une application. On ne peut pas le faire
à partir d'une applet pour des questions de sécurité. On remarquera également qu'à tout accès au
système de fichiers, il peut se produire une exception java.io.IOException.
L'exemple ci-dessous montre comment obtenir des informations sur un fichier. Notons qu'il n'existe
pas de classe spéciale pour traiter des répertoires, ceux-ci sont considérés comme des fichiers.
4.1. Informations sur un fichier
import java.io.*;

class fileInfo {
public static void main (String args[]) throws java.io.IOException {
System.out.println("Entrez le repertoire ou se trouve le fichier : ");
char ch;

// directory
13
Langages de Programmation Parallèle, Java & Simula.
StringBuffer dirBuf = new StringBuffer();
while ((ch = (char)System.in.read()) != '\n')
dirBuf.append(ch);
File dir = new File(dirBuf.toString());

// file
System.out.println("Entrez le nom du fichier : ");
StringBuffer fileBuf = new StringBuffer();
while ((ch = (char)System.in.read()) != '\n')
fileBuf.append(ch);
File fil = new File(dir,fileBuf.toString());
if (fil.exists()) {
System.out.println("Fichier trouve");
System.out.println("Nom de fichier : "+fil.getName());
System.out.println("Chemin du fichier : "+fil.getPath());
System.out.println("Chemin absolu : "+fil.getAbsolutePath());
System.out.println("Droit de lecture : "+fil.canRead());
System.out.println("Droit d'ecriture : "+fil.canWrite());
System.out.println("\nContenu du repertoire :");
String listing[] = dir.list();
for (int i = 0;i < listing.length;i++)
System.out.println(listing[i]);
} else {
System.out.println("Fichier absent");
}
}
}

4.2. Copieur de fichiers


La lecture et l'écriture se fait en utilisant les différentes classes de traitement des flux (streams).
L'exemple ci-dessous copie un fichier source dans un fichier destination, après avoir effectué un
certain nombre de tests pour vérifier que l'opération de copie est possible. Les classes utilisées sont
FileInputStream et FileOutputStream du paquetage java.io. Ces deux classes étendent java.io.InputStream.

import java.io.*;

class fileCopy {
public static void main (String args[]) throws java.io.IOException {
char ch;

// repertoire source

System.out.println("repertoire source : ");


StringBuffer dirBuf = new StringBuffer();
while ((ch = (char)System.in.read()) != '\n')
dirBuf.append(ch);
File srcDir = new File(dirBuf.toString());

if (!srcDir.exists()) {
System.out.println("repertoire absent");
System.exit(1);
} else if (!srcDir.canRead()) {
System.out.println("repertoire illisible");
System.exit(1);
}

// fichier source

System.out.println("fichier source : ");


StringBuffer fileBuf = new StringBuffer();
while ((ch = (char)System.in.read()) != '\n')
fileBuf.append(ch);
File srcFile = new File(srcDir,fileBuf.toString());

if (!srcFile.exists()) {
System.out.println("fichier absent");
System.exit(1);
} else if (!srcFile.canRead()) {
System.out.println("fichier illisible");

14
Langages de Programmation Parallèle, Java & Simula.
System.exit(1);
}

// repertoire destination

System.out.println("repertoire destination : ");


dirBuf = new StringBuffer();
while ((ch = (char)System.in.read()) != '\n')
dirBuf.append(ch);
File dstDir = new File(dirBuf.toString());

if (!dstDir.exists()) {
System.out.println("repertoire absent");
System.exit(1);
} else if (!dstDir.canWrite()) {
System.out.println("repertoire protege en ecriture");
System.exit(1);
}

// fichier destination

System.out.println("fichier destination : ");


fileBuf = new StringBuffer();
while ((ch = (char)System.in.read()) != '\n')
fileBuf.append(ch);
File dstFile = new File(dstDir,fileBuf.toString());

if (dstFile.exists()) {
System.out.println("fichier existant");
System.exit(1);
}

// copie du fichier

FileInputStream inStream = new FileInputStream(srcFile);


FileOutputStream outStream = new FileOutputStream(dstFile);

// tant que ce n'est pas la fin du fichier


while (inStream.available() > 0)
outStream.write(inStream.read());

// fermeture des fichiers


inStream.close();
outStream.close();
}
}

5. Java en réseau
On ne peut pas parler du langage Java sans présenter son aspect « réseautique ». En effet Java
s’appuie dans sa gestion des connexions réseaux sur des classes spécifiques qui gèrent les différents
ports TCP/IP et modélisent l’architecture client-serveur.
5.1. Les connexions réseau TCP/IP - la classe Socket
Avec Java, il est très facile d'ouvrir une connexion TCP/IP depuis une applet ou une application. La
classe Socket fournit tous les outils nécessaires à cela. Une fois qu'une connexion est réalisée avec
une autre machine, on a accès à un flux d'entrée et un flux de sortie, comme si l'on travaillait avec
un fichier.
Exemple
import java.io.*;
import java.net.*;

public class jnetcat {

public static void main(String args[]) {


if (args.length != 2)
System.out.println("usage : jnetcat hote port");
else {
Socket sk = null;
15
Langages de Programmation Parallèle, Java & Simula.
try {
sk = new Socket(args[0],Integer.valueOf(args[1]).intValue());
DataInputStream is = new DataInputStream(sk.getInputStream());
String ligne;
while ((ligne = is.readLine()) != null)
System.out.println(ligne);
} catch (UnknownHostException e) {
System.out.println("hote inconnu : "+e.getMessage());
} catch (IOException e) {
System.out.println("erreur entree/sortie : "+e.getMessage());
}
}
}
}
Cette application se connecte sur la machine et le port spécifiés sur la ligne de commande. Elle lit
ensuite toutes les données, et les affiche à l'écran. On peut par exemple exécuter :
java jnetcat eig6.unige.ch 13
Exécution - port 13
Thu May 30 11:23:19 1996

Le port 13 donne l'heure, on parle du généralement du port daytime.


Exécution - port 19
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefg
!"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefgh
"#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghi
#$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghij
$%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijk
%&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijkl
&'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklm
'()*+,-./0123456789:;<=>?@ABCDEFGHIJKLMNOPQRSTUVWXYZ[\]^_`abcdefghijklmn
Le port 19 est le port générateur de caractères, chargen, il envoie des lignes de caractères tant que le
client est connecté. Pour arrêter le client, on doit presser contrôle-C.
5.2. Les serveurs TCP/IP - SocketServer
Nous allons voir comment réaliser une application serveur TCP/IP. La classe SocketServer permet de
se mettre en attente d'un client sur un port. La méthode SocketServer.accept() retourne un objet de la
classe Socket lorsqu'un client veut se connecter. C'est grâce à cet objet que l'on pourra établir une
connexion avec le client.

Serveur d'heure
import java.net.*;
import java.io.*;
import java.util.Date;

class serveurHeure {

public static void main(String args[]) {


try {
ServerSocket sks = new ServerSocket(11111);
Socket sk = sks.accept();
System.out.println(sk.toString());
DataOutputStream out = new DataOutputStream(sk.getOutputStream());
out.writeBytes("Bienvenue "+sk.getInetAddress().getHostName()+
". Il est actuellement "+new Date()+"\n");
sk.close();
sks.close();
} catch (IOException e) {
System.out.println(e.getMessage());
}
}
}
Le résultat que l'on peut voir apparaître sur la console où l'on a lancé le serveur est :
Socket[addr=129.194.184.2,port=2569,localport=11111]

16
Langages de Programmation Parallèle, Java & Simula.

Pour se connecter à ce serveur, on peut utiliser telnet ou l'utilitaire jnetcat qui a été décrit
précédemment. Le client qui se connecte sur le port 11111 du serveur peut voir apparaître un
message du type :
Bienvenue eig. Il est actuellement Thu May 30 14:12:35 MET DST 1996

Généralement, on désire pouvoir servir plusieurs applications clientes à partir du même serveur,
comme c'est le cas pour FTP et HTTP. Nous allons voir comment implémenter un serveur multi-
thread, qui crée un processus par client. Nous déclarons une classe connexion qui est capable de
traiter les entrées/sorties d'un client que nous instancions chaque fois qu'un client se connecte sur le
port serveur.
Le programme ci-dessous est un exemple de serveur qui peut s'accommoder de plusieurs clients
concurremment. Lorsqu'un client s'y connecte, le serveur initialise un compteur à 0, et chaque
nombre que le client lui envoie est additionné à ce compteur.

Serveur multi-clients
/* Serveur multi-clients
* Additionne tous les nombres fournis par les clients
*/

import java.io.*;
import java.net.*;

public class serveurAdd {


static int port;

public static void main(String args[]) {


if (args.length != 1)
System.out.println("usage : serveurAdd port");
else {
ServerSocket serveur;
try {
port = Integer.parseInt(args[0]);
} catch (NumberFormatException e) {
System.out.println("le port doit etre une valeur entre 1024 et
65535");
System.exit(1);
}
try {
serveur = new ServerSocket(port);
for (;;)
new connexion(serveur.accept());
} catch (IOException e) {
System.out.println("erreur a la creation d'un objet Socket :
"+e.getMessage());
System.exit(1);
}
}
}

class connexion implements Runnable {

Socket client;
DataInputStream depuisClient;
PrintStream versClient;
int somme = 0;

public connexion(Socket client) {


this.client = client;

try {
// creation des flux de/vers le client
depuisClient = new DataInputStream(client.getInputStream());
versClient = new PrintStream(client.getOutputStream());

17
Langages de Programmation Parallèle, Java & Simula.
// message d'accueil
versClient.println("Bienvenue sur le serveur additionneur");
versClient.println("Entrez un nombre suivi de entree");
} catch (IOException e) {
try { client.close(); } catch (IOException ee) {}
}
new Thread(this).start();
}

public void run() {


try {
String lue;
while ((lue = depuisClient.readLine()) != null) {
somme += Integer.parseInt(lue);
versClient.println("merci. La somme est maintenant "+somme);
}
versClient.println("Au revoir !");
} catch (IOException e) {
System.out.println("Exception entree/sortie : "+e.getMessage());
} catch (NumberFormatException e) {
versClient.println("Seul les nombres sont autorises. Au revoir !");
}
stop();
}

public void stop() {


try {
client.close();
} catch (IOException e) {
System.out.println("exception a la fermeture d'une connexion : "+e);
}
}
}

6. Parallélisme sous Java


Java offre un certain nombre de classe et de d’outils pour concevoir et exécuter des tâches
concurrentes ou parallèles. La machine virtuelle Java, et grâce son aspect multi-plate-forme, fournit
au programmeur un environnement souple, portable et assez puissant pour développer des
applications parallèles. Le langage est extensible est peut être facilement mieux adapté à certains
besoin en affinant les classes prédéfinies.
Nous examinerons, dans ce qui suit, les notions fondamentales du mécanisme de parallélisme
adopté par le langage avec des exemples pilotes très utiles.
6.1. Processus légers (threads)
Java permet l'exécution concurrente de plusieurs processus légers threads, c'est-à-dire plusieurs
tâches. On différencie généralement les processus légers des processus lourds. Les premiers sont
internes à une application alors que les processus lourds sont vus par le système d'exploitation
comme plusieurs processus. Les processus légers partagent un espace d'adressage commun. Comme
on le sait, programmation concurrente sous-entend mécanismes de synchronisation et d'exclusion
mutuelle.
Le mécanisme d'exclusion mutuelle présent dans l'environnement Java est le moniteur, que l'on
implémente grâce au modificateur synchronized. Le principe de base est qu'un seul processus ne peut
entrer dans un moniteur si celui-ci est occupé.
Mais voyons tout d'abord comment créer plusieurs processus. Nous pouvons tout simplement
étendre la classe java.lang.Thread et remplacer sa méthode run() par notre propre algorithme. Lorsque
l'on appelle la méthode start(), la méthode run() est déclenchée, et elle s'arrête lors d'un appel à stop().
Exemple
class afficheur extends Thread {

/** constructeur permettant de nommer le processus */


public afficheur(String s) {
super(s);
}

18
Langages de Programmation Parallèle, Java & Simula.

/** affiche son nom puis passe le controle au suivant */


public void run() {
while (true) {
System.out.println("je suis le processus "+getName());
Thread.yield(); // passe le controle
}
}
}

class thread12 {

public static void main(String args[]) {

afficheur thread1 = new afficheur("1");


afficheur thread2 = new afficheur("2");

thread1.start();
thread2.start();

while (true) {
System.out.println("je suis la tache principale !");
Thread.yield();
}
}
}
Exécution
je suis la tache principale !
je suis le processus 1
je suis le processus 2
je suis la tache principale !
je suis le processus 1
je suis le processus 2
je suis la tache principale !
je suis le processus 1
je suis le processus 2
je suis la tache principale !
je suis le processus 1
je suis le processus 2
je suis la tache principale !
je suis le processus 1
je suis le processus 2
je suis la tache principale !
je suis le processus 1
je suis le processus 2
je suis la tache principale !
je suis le processus 1
je suis le processus 2
je suis la tache principale !
je suis le processus 1
je suis le processus 2
Le problème est que la méthode Thread.stop() est qualifiée de final. On ne peut donc pas la remplacer.
Une autre solution est possible. L'interface Runnable permet de créer un objet, que l'on utilisera
ensuite comme constructeur d'un Thread. C'est la méthode start() de l'objet qui sera responsable de la
création et de l'activation du thread.
Exemple
class afficheurRunnable implements Runnable {

boolean cont = true;


String nomProcessus;
Thread th;

/** constructeur permettant de nommer le processus */


public afficheurRunnable(String s) {
nomProcessus = s;
}
19
Langages de Programmation Parallèle, Java & Simula.

public void start() {


if (th == null) {
th = new Thread(this,nomProcessus);
th.start();
}
}

public void stop() {


if (th != null) {
th.stop();
th = null;
}
}

/** affiche son nom puis passe le controle au suivant */


public void run() {
while (cont) {
System.out.println("je suis le processus "+th.getName());
Thread.yield(); // passe le controle
}
}

class runnable12 {

public static void main(String args[]) {

afficheurRunnable run1 = new afficheurRunnable("1");


afficheurRunnable run2 = new afficheurRunnable("2");

run1.start();
run2.start();

while (true) {
System.out.println("je suis la tache principale !");
try {
Thread.sleep(20);
} catch (InterruptedException e) {}
}
}

}
Exécution
je suis la tache principale !
je suis le processus 1
je suis le processus 2
je suis le processus 1
je suis le processus 2
je suis le processus 1
je suis le processus 2
je suis le processus 1
je suis le processus 2
je suis la tache principale !
je suis le processus 1
je suis le processus 2
je suis le processus 1
je suis le processus 2
je suis le processus 1
Attention !
L'implémentation et le comportement de l'Ordonnanceur de processus (scheduler) n'est pas spécifié
par Sun. Cela veut dire que les différentes machines virtuelles (MV) Java n'auront pas forcément le
même comportement. Une MV peut se comporter comme un noyau temps réel (pas de timeslicing)
ou comme un noyau préemptif.

20
Langages de Programmation Parallèle, Java & Simula.

Comme nous l'avons vu dans les exemples précédents, on utilise Thread.yield() afin de redonner le
contrôle à l'ordonnanceur. De cette manière, quel que soit le comportement de la machine virtuelle
Java, les différents processus s'entrelaceront.
Il est également important de s'assurer que les accès concurrents aux variables et méthodes ne
mettent pas en péril la validité des données. Pour ce faire, veuillez vous reporter à la page décrivant
les mécanismes d'exclusion mutuelle de Java.
6.2. Exclusion mutuelle
Le mécanisme d'exclusion mutuelle présent dans Java est le moniteur. Si l'on désire définir une
section critique, afin de s'assurer de la cohérence des données, nous devons utiliser le mot-clé
synchronized. C'est de cette manière que l'on crée un moniteur.
Lorsque l'on crée une instance d'une certaine classe, on crée également un moniteur qui lui est
associé. Lorsque l'on applique le modificateur synchronized, on place la méthode (le bloc de code)
dans ce moniteur, ce qui assure l'exclusion mutuelle.
Exemple
class mutexAcc {

int accumulateur = 0;

public synchronized void stocke(int val) {


accumulateur += val;
}

public int lit() {


return accumulateur;
}
}
L'opération d'incrémentation += prend plusieurs instructions, et un changement de contexte entre
elles pourrait créer une incohérence des résultats. Le modificateur synchronized indique que la
méthode stocke fait partie du moniteur de l'instance, empêchant son exécution simultanée par
plusieurs processus. Si le moniteur est déja occupé, les processus suivants seront mis en attente.
L'ordre de réveil des processus n'est pas déterministe.

L'utilisation de méthodes synchronisées trop longues peut créer une baisse d'efficacité. Avec Java,
il est possible de placer n'importe quel bloc dans un moniteur, ce qui permet ainsi de réduire la
longueur des sections critiques. Dans l'exemple suivant, le code de methode1 et de methode2 est
équivalent.
synchronized void methode1() {
// section critique...
}

void methode2() {
synchronized(this) {
// section critique...
}
}
Exemple
public class Tortue {

private Point pos;


private int angle;

public Tortue(int x,int y,int angle) {


// ...
}

public synchronized void tourne(int degres) {


angle += degres;
}

public synchronized void avance(int distance) {


pos.x += (int) ((double)distance*Math.cos((double)angle));

21
Langages de Programmation Parallèle, Java & Simula.
pos.y += (int) ((double)distance*Math.sin((double)angle));
}

public int angle() {


return angle;
}

public synchronized Point pos() {


return new Point(pos.x,pos.y);
}
}

public class mutexLogo {

public int lectureEntier() {


// ...
// lecture d'un nombre entier
// pouvant prendre du temps
// ...
}

public static void carre(Tortue tortue) {


int largeur = lectureEntier();
int longueur = lectureEntier();
synchronized (tortue) {
tortue.tourne(90);tortue.avance(largeur);
tortue.tourne(90);tortue.avance(longueur);
tortue.tourne(90);tortue.avance(largeur);
tortue.tourne(90);tortue.avance(longueur);
}
// autres taches...
}
}
Comme nous l'avons vu, quand le modificateur synchronized qualifie une méthode, on place cette
méthode dans le moniteur de l'instance. Cela permet de sécuriser l'accès à une instance particulière
d'un objet. En créant un bloc synchronized (tortue), on entre dans le moniteur associé à l'instance tortue.
Dans l'exemple ci-dessus, si un processus est en train d'afficher un carré en faisant appel à
mutexLogo.carre(tortue), un autre processus ne pourra pas déplacer la tortue. Malgré tout, pendant que
l'on se trouve dans la méthode carre et que l'on est en train de lire les dimensions du carré, un autre
processus pourra utiliser la tortue. En effet, le moniteur n'est pas occupé, parce que la méthode n'est
pas synchronized, seul un bloc l'est.

Considérons maintenant le cas où il faut sécuriser l'accès à une variable de classe. La solution est
simple, il faut créer un moniteur qui est commun à toutes les instances de la classe. La méthode
getClass() retourne la classe de l'instance dans laquelle on l'appelle. Nous pouvons maintenant créer
un moniteur qui utilise le résultat de getClass() comme "verrou".
class mutexStatic {
private int accumulateur = 0;
private static int acces = 0;

public synchronized void stocke(int val) {


accumulateur += val;
synchronized (getClass()) {
acces += 1;
}
}

public int lit() {


synchronized (getClass()) {
acces += 1;
}
return accumulateur;
}

public int acces() {


return acces;

22
Langages de Programmation Parallèle, Java & Simula.
}
}
Cet exemple définit une classe d'accumulateur qui incrémente acces chaque fois que l'on accède à
stocke ou à lit. La variable acces est une variable de classe (déclarée public), elle est donc partagée par
les différentes instances de cette classe. La méthode getClass() retourne un objet de type Class avec
lequel on crée un nouveau moniteur.
6.3. Synchronisation : les méthodes wait() et notify()
Il peut être nécessaire de synchroniser des processus qui accèdent aux mêmes ressources.
L'utilisation des moniteurs permet de garantir l'exclusion mutuelle, et pour la synchronisation, on
utilisera des signaux, qui sont modélisés par les méthodes wait() et notify().
Un des exemples classiques de l'utilisation des signaux est celui des producteurs-consommateurs.
Prenons un tampon borné de n objets, un processus producteur et un processus consommateur.
Exemple - tamponCirc.java
class tamponCirc {
private Object tampon[];
private int taille;
private int en, hors, nMess;

/** constructeur. crée un tampon de taille éléments */


public tamponCirc (int taille) {
tampon = new Object[taille];
this.taille = taille;
en = 0;
hors = 0;
nMess = 0;
}

public synchronized void depose(Object obj) {


while (nMess == taille) { // si plein
try {
wait(); // attends non-plein
} catch (InterruptedException e) {}
}
tampon[en] = obj;
nMess++;
en = (en + 1) % taille;
notify(); // envoie un signal non-vide
}

public synchronized Object preleve() {


while (nMess == 0) { // si vide
try {
wait(); // attends non-vide
} catch (InterruptedException e) {}
}
Object obj = tampon[hors];
tampon[hors] = null; // supprime la ref a l'objet
nMess--;
hors = (hors + 1) % taille;
notify(); // envoie un signal non-plein
return obj;
}
}
Exemple - utiliseTampon.java
class producteur extends Thread {

private tamponCirc tampon;


private int val = 0;

public producteur(tamponCirc tampon) {


this.tampon = tampon;
}

public void run() {


while (true) {
System.out.println("je depose "+val);
23
Langages de Programmation Parallèle, Java & Simula.
tampon.depose(new Integer(val++));
try {
Thread.sleep((int)(Math.random()*100)); // attend jusqu'a 100 ms
} catch (InterruptedException e) {}
}
}
}

class consommateur extends Thread {

private tamponCirc tampon;

public consommateur(tamponCirc tampon) {


this.tampon = tampon;
}

public void run() {


while (true) {
System.out.println("je preleve
"+((Integer)tampon.preleve()).toString());
try {
Thread.sleep((int)(Math.random()*200)); // attends jusqu'a 200 ms
} catch (InterruptedException e) {}
}
}
}

class utiliseTampon {

public static void main(String args[]) {

tamponCirc tampon = new tamponCirc(5);


producteur prod = new producteur(tampon);
consommateur cons = new consommateur(tampon);

prod.start();
cons.start();
try {
Thread.sleep(30000); // s'execute pendant 30 secondes
} catch (InterruptedException e) {}
}
}
Exécution
...
je depose 165
je depose 166
je preleve 161
je depose 167
je preleve 162
je depose 168
je preleve 163
je depose 169
je preleve 164
je depose 170
je preleve 165
je depose 171
je preleve 166
je preleve 167
...
Nous déclarons 2 processus, un producteur et un consommateur. Les données sont produites plus vite
que le consommateur ne peut les prélever, à cause de la différence de durée des délais (aléatoires)
introduits dans les 2 processus.
Il existe deux variantes de la méthode wait() qui permettent de spécifier un temps limite après lequel
le processus sera réveillé, sans avoir à être notifié par un processus concurrent. Il s'agit de wait(long
milli), qui attend milli millisecondes, et de wait(long milli,int nano) qui attend nano nanosecondes en

24
Langages de Programmation Parallèle, Java & Simula.

plus du temps milli. Il n'est pas possible de savoir si wait() s'est terminé à cause d'un appel à notify()
par un autre processus, ou de l'épuisement du temps.
La méthode notifyAll() réveille tous les processus, et dès que le moniteur sera libre, ils se réveilleront
tour à tour.
Attention !
Le noyau Java ne fait aucune garantie concernant l'élection des processus lors d'un appel à notify().
En particulier, il ne garantit pas que les processus seront débloqués dans l'ordre ou ils ont été
bloqués. C'est à cause de cela que l'on a placé wait() dans une boucle dans l'exemple précédent, car
un consommateur pourrait réveiller un autre consommateur alors que le tampon est vide.
Remarquons aussi que les méthodes wait, notify() et notifyAll() ne peuvent être appelées que dans des
méthodes synchronisées (synchronized).
6.4. L’interface Runnable
Les applets peuvent utiliser des processus en implémentant l'interface Runnable qui demande une
méthode run. Le processus doit être associé à l'applet par son constructeur, sa méthode start appelle
alors la méthode run de l'applet [3].
Squelette d'applet implémentant l'interface Runnable
L'applet gère un processus à travers une variable de type Thread. Sa méthode start, appelée juste
après init, crée le processus et le démarre, ce qui provoque l'exécution de la méthode run. Celle-ci
est formée d'une boucle dans laquelle se fait le travail du processus, avec à chaque tour un arrêt
(méthode wait) permettant au processus principal de l'applet de s'exécuter pour la gestion des
évènements (affichage, souris, boutons, etc...). Une variable speed permet de gérer le temps d'arrêt
qui est exprimé en millièmes de secondes. Enfin nous utilisons la méthode stop de l'applet, qui est
appelée lors de la fermeture de la page html, pour stopper le processus.

import java.awt.*;
import java.applet.*;

public class MaClasse extends Applet


implements Runnable {

private Thread processus=null;


int speed=20;

public void init() {


}

public void start() {


if (processus==null) {
processus=new Thread(this);
processus.start();
}
}

public void stop() {


processus.stop();
processus=null;
}

public void run() {


while (processus!=null) {
repaint();
//modifications du dessin
try {
processus.sleep(speed);
} catch (InterruptedException e){}
}
processus=null;
}
}

25
Langages de Programmation Parallèle, Java & Simula.

Création d'un chronomètre


A titre d'exemple, créons une applet qui affiche un chronomètre en utilisant le modèle précédent.
Comme le chronomètre affiche les dixièmes de secondes, la variable speed est fixée à 100.
Une variable ds compte les dixièmes de secondes. Le travail du processus se limite donc à
incrémenter cette variable et à réafficher le chronomètre.

import java.awt.*;
import java.applet.*;

public class Chrono1 extends Applet


implements Runnable {

private Thread chronometre;


int speed=100;
private int ds=0; //dixième de seconde

public void init() {


setBackground(Color.white);
setForeground(Color.blue);
setFont(new Font("SansSerif",Font.BOLD, getSize().height));
}

public void start () {


// Au début de l'applet, création et démarrage du chronomètre
if (chronometre==null) {
chronometre=new Thread(this);
chronometre.start();
}
}

public void run () {


while (chronometre!=null) {
repaint ();
ds++;
try {
chronometre.sleep(speed);
} catch (InterruptedException e) {}
}
}

public void stop () {


// A la fin de l'applet, arrêt du chronometre
chronometre.stop();
chronometre=null;
}

public void paint (Graphics g) {


// Dessine le temps écoulé sous forme de h:mm:ss:d
String S=ds/36000 +":"+(ds/6000)%6+(ds/600)%10
+":"+(ds/100)%6+(ds/10)%10+ ":" + ds%10;
//affichage centré
FontMetrics fm=g.getFontMetrics();
int largeur=fm.stringWidth(S);
int hauteur=fm.getHeight();
int x=(getSize().width-largeur)/2;
int y=(getSize().height-hauteur)/2;
g.drawString(S, x, y+hauteur-fm.getDescent());
}
}

26
Langages de Programmation Parallèle, Java & Simula.

En utilisant le modèle fourni précédemment, il nous a suffi d'écrire les méthodes init (choix des
couleurs) et paint (affichage), puis de compléter la méthode run pour incrémenter le nombre de
secondes [3].

Nous présentons ci-contre


une exécution de l’applet
chrono.

27
Langages de Programmation Parallèle, Java & Simula.

Partie II : SIMULA

1- Introduction
SIMULA est le premier langage orienté-objet conçu initialement pour créer des environnements de
simulation assez puissant pour modéliser des problèmes réels (système de production, simulation et
conception assistée par ordinateur, …)
En effet, SIMULA a été le premier langage à utiliser le concept d'OBJET; et ce depuis 1967. Le
concept a ensuite été repris pour la programmation graphique interactive par Smalltalk. Ce qui a
vraiment lancé la programmation objet sur le marche nord-américain, c'est C++, un C amélioré
conçu par B.Stroustrup, un fan de Simula qui voulait faire de la simulation dans l'environnement C
de Bell_Labs. Et donc, 20 ans après son invention par Dahl et Nygaard [1], la programmation objet
a été redécouverte par le reste du monde. La référence de base reste la norme du « Swedish
Standards Institute ».
Simula67 était à peu de choses près identique au Simula d'aujourd'hui - le standard a été mis à jour
en 1987 - et c'était le premier langage orienté-objet. Conçu pour décrire (et programmer) des
systèmes complexes comportant des activités parallèles, Simula67 est basé sur le concept de Class
(type abstrait) décrivant les attributs et méthodes d'un ensemble d'objets. L'héritage simple entre
classes est exprimé en préfixant la définition d'une sous-classe par le nom de sa sur-classe [5].
Nous présentons dans cette partie les notions de base du langage Simula, les structure et la syntaxe
la plus utilisée, et surtout son aspect de simulation et de parallélisme [5].

2. Description générale
Voici un programme complet qui montre l'allure de SIMULA . Ceci nous permettra de faire
quelques commentaires généraux.
begin
! programme simple ;
integer i,j;
integer carre ;
i:= inint; j:= inint;
carre := i*i;
outtext(" i carre somme"); outimage;
outint(i,5);
outint(carre,5);
outint(i+j, 6);
outimage;
end

Un programme SIMULA est un BLOC selon la syntaxe :


BEGIN
< declarations >
< enonces >
END
A part ce fait, ce programme n'est pas très différent de programmes en Pascal ou on C. Notez,
cependant, que les délimiteurs de commentaires ("!" et ";"), et ceux pour les chaînes de textes sont
différents de ceux de PASCAL.
2.1. Déclarations de base
Les types de base sont: entier, réel, booléen, caractère, référence (pointeur) et texte (chaîne de
caractères). Toute variable est initialisée automatiquement à une valeur neutre: 0, false, none (nil)
ou notext (chaîne vide). Voici le style des diverses déclarations:
28
Langages de Programmation Parallèle, Java & Simula.

Pascal Simula Constantes


I: integer; integer I; 12
R: real; real R ; 13.4 1.3&-5 1.3&&-5
long LR;
B: boolean; boolean B; true false
C: car; character C; 'C'
P: ^xxx; ref(xxx) P; none
T: alfa; text T; "chaine"

On peut aussi déclarer des constantes en ajoutant une valeur dans la déclaration:
Simula Pascal
const
nMax = 100; integer nMax = 100;
Pi = 3.1416; real Pi = 3.1416;

Le compilateur utilise ces déclarations pour:

1. vérifier le sens des opérations (p.e. empêcher l'addition d'un texte et d'un booléen)
2. réserver des zones de mémoire pour stocker les informations

2.2. Entrée/Sorties
Les entrées/sorties sont en format libre et portent sur les entiers, réels, caractères et chaînes. Pour
chaque type on a une fonction pour l'entrée et une procédure pour la sortie qui transfèrent une
valeur à la fois. Pour les écritures, on doit spécifier la taille des champs.

Lecture Impression
Integer I:= inint ; outint(I,10) ;
Real X:= inreal ; outreal(X,2,10);
Character C:= inchar ; outchar( C ) ;
Text T:= intext(20) ; outtext(" OK !") ;

Entrées/Sorties en Simula

Sauter à la ligne Outimage ;


Afficher sans saut de ligne BreakOutimage ;
Test de fin de fichier B := endfile ;
Test de fin de données B := lastitem ;

Autres opérations

Pascal Simula
read(I,R,C); I:= inint;
R:= inreal;
C:= inchar;

readln; inimage; inimage;


B:= eof; B:= endfile; (vrai si on est a la fin)
B:= lastitem; (on saute les blancs avant
de decider)

29
Langages de Programmation Parallèle, Java & Simula.
writeln(I,R:10:2,C,'OK'); outint(I,5);
outreal(R,2,10); ...gare à l'ordre
outchar(C);
outtext("OK");
outimage;

3. Calcul arithmétique
Les calculs se font par des énoncés d'affectations de la forme suivante:
variable := expression
Cet énoncé veut dire qu'on calcule d'abord la valeur de l'expression; puis on stocke cette valeur dans
la variable. Par exemple:
y := x + 1
veut dire: ajouter 1 à la valeur de x et mettre le résultat dans la variable y. Si x valait 4, alors, après
cet énoncé, y vaudra 5. Dans le cas d'expressions de valeurs numériques, les variables ( x et y dans
l'exemple) doivent être de type integer ou real.
Notez que les caractères de l'opérateur d'affection, ":=", doivent être collés côte à côte et la combinaison ":=" compte comme
un seul symbole en Simula.
3.1. Opérateurs
Dans les expressions arithmétiques, les opérateurs sont sensiblement les mêmes qu'en
mathématiques:
addition: "+"
soustraction: "-"
multiplication: "*"
division réelle: "/"
division entière: "//" ( et la fraction est tronquée )
exponentiation: "**" X ** i veut dire X à la puissance i
Ici encore les caractères des opérateurs composés, "//" et "**", doivent être côte à côte.
Remarquez que la multiplication, qui est souvent implicite en notation mathématique, doit être
notée explicitement. Donc l'expression mathématique (4a + b)(3c + d) devra être écrite comme suit
en Simula:
(4 * a + b) * ( 3 * c + d )
En Simula, il y a deux opérateurs de division, "/" et "//". L'opérateur "/" dénote la division usuelle et
le résultat est toujours de type real:
4 / 3 donne 1.333333...
et 4 / 2 donne 2.0
L'opérateur "//" dénote la division entière. Les opérandes doivent être de type integer et le résultat
sera un integer. Si résultat exact de la division comporte une fraction, celle-ci est ignorée. Donc,
4 // 2 donne 2
11 // 3 donne 3
-7 // 3 donne -2
7 // -3 donne -2
5 // 1.3 donne ERREUR car 1.3 n'est pas un entier
On peut récupérer le reste de la division de p // q avec la fonction rem(p, q). Reprenant les mêmes
exemples de division:
rem( 4,2 ) donne 0
rem(11,3 ) donne 2
rem(-7,3 ) donne -1
rem(7,-3 ) donne 1

Note:
1. Le RESTE de la division a toujours le même signe que le dividende.
2. La relation suivante existe entre les opérateurs "//" et "rem":

(A//B) * B + rem(A,B) = A , c.a.d. Quotient * Diviseur + Reste = Dividende.

30
Langages de Programmation Parallèle, Java & Simula.

Il existe une autre fonction qui est reliée à la division; c'est mod(P,Q) qui donne la fonction
mathématique du modulo. Si P et Q sont positifs alors rem(p,q) et mod(p,q) donne le même
résultat; Autrement, la relation suivante est vraie:
entier(A/B) * B + mod(A,B) = A

Note: entier(X) est équivalent à floor(X).

Le dernier opérateurs que nous avons étudié est l'exponentiation: "**". En général, le
résultat de l'exponentiation est une valeur real. Le seul cas où le résultat est un
integer, c'est quand les deux opérandes sont de type integer. Dans ce cas, l'exposant
doit être zéro ou positif.

2.0 ** 3 donne 8.0


9 ** 0.5 donne 3.0
9.1 ** 0 donne 1.0
4.0 ** (-2) donne 0.5
2 ** 3 donne 8
2 ** 0 donne 1
2 ** (-1) donne ERREUR

Voici la priorité des opérateurs (y compris les opérations logiques):


1. **
2. * / //
3. + - &
4. = <> > >= < <= == =/=
5. not
6. and
7. or
8. impl
9. eqv
10. and then 11. or else

Note:
• & sert à la concaténation de chaînes de caractères
• pour les Booléens, l'égalité est codée avec EQV (équivalent) et non avec '='
• == et =/= servent à comparer l'égalité de pointeurs
Une expression peut aussi utiliser des procédures utilitaires comme sin (sinus), sqrt (racine carrée)
ou ln (logarithme naturel).
L'annexe 2 donne une brève description des procédures utilitaires qui sont fournies avec tout
système Simula. L'exemple ci-dessous fait des calculs numériques:
begin
real x, y, z;
outtext("Y et Z svp: "); breakoutimage;
y := inreal;
z := inreal;
x := sqrt( y ** 2 + ln(z) ** 2);
outtext("X = ");
outreal(x,3,10);
outimage;
end

3.2. Types numériques


En Simula, nous avons 2 types numériques de base: les entiers (integer) et les réels (real). Les
valeurs entières s'écrivent avec juste des chiffres (et possiblement un signe) tandis que les réelles
s'écrivent avec soit un point décimal ou un exposant ("&"). Par exemple:
entiers: 0 12 -456
réels: 0.0 1.234&-4 &6

31
Langages de Programmation Parallèle, Java & Simula.

Les entiers servent à compter les choses ou faire des calculs d'adresses. Les réels sont utilisés pour
des calculs scientifiques, où pour lesquels on utilise des fractions (p.e. 3.14159), des valeurs très
grandes comme la constante d'Avogadro (6.024&23 atomes/mole) ou très petites, comme la masse
d'un électron (9.1083&-28 g).

Conversion de type
Typiquement, on mettra des valeurs entières dans des integer et des réels dans des real; mais on
peut mélanger les types dans une expression ou affecter une valeur d'un type à une variable de
l'autre type. Dans ce cas, il y a conversion automatique de la valeur d'un type à l'autre. En général,
quand on passe d'un entier à un réel, la valeur est inchangée. Par contre, quand on passe d'un réel à
un entier, la fraction est enlevée pour faire un entier. Dans certain langage la valeur est tronquée; en
Simula, la valeur est arrondie et on retourne l'entier le plus proche.
integer i;
real x;

i := 123.45; ! i vaut 123 ;


i := 123.5; ! i vaut 124 ;
i := -3.6 ; ! i vaut -4 ;
x := 3; ! x vaut 3.0 ;
Il existe une fonction en Simula, entier, qui permet de passer d'un réel à un entier d'une façon
différente. entier implante la fonction connue parfois sous le nom de floor: entier(x) retourne l'entier
maximum qui est plus petit ou égal à "x":
entier( 3.4 ) ==> 3
entier(-3.4 ) ==> -4

Expressions de type mixte


Pour les 3 opérateurs 'simples' (+, - et *), le type du résultat dépend de celui des opérandes. Si les
deux opérandes sont de type integer alors le résultat est integer; si une des opérandes est real alors
le résultat est real.
1 + 2 ==> 3
1.0 + 2 ==> 3.0
par contre, nous avons vu que certains opérateurs arithmétiques exigent et/ou retournent des valeurs
d'un certains type. Par exemple, la division "/" retourne toujours un real; la division entière exige
des opérandes entiers et retourne un entier.
4 / 2 ==> 2.0
rem( 4.0, 2) ==> Erreur car 4.0 n'est pas un entier
Une catégorie importante de fonctions standards sont disponibles pour faire des conversions entre le
type integer et les autres types.

• RANK(C) : donne code entier du caractere C


• CHAR(N) : donne caractere dont le code est N
• LETTER(C): vrai si C est une lettre
• DIGIT(C): vrai si C est un chiffre
• ENTIER(R): donne entier maximum dont valeur <= R ( un peu diffèrent de TRUNC)

Limites sur les valeurs


Toute information sur ordinateur est stockée dans des cases mémoire de taille fixe et donc le
nombre de valeurs distinctes qui peuvent être représenté est fini.
Pour les entiers sur nos machines, ceci veut dire que 2^32 valeurs sont possibles et elles vont de -
2147483648 à 2147483647. Deux constantes prédéfinies, maxInt et minInt, représentent ces
valeurs.
Les réels sont stockés avec (environ) 7 chiffres de précision et les valeurs vont de 3.4 x 10^38 à 1.1
x 10^-38.

32
Langages de Programmation Parallèle, Java & Simula.

4. Autres déclarations
Pascal a une structure très élaborée de déclarations. SIMULA est moins riche mais plus tolérant.
L'ordre des déclarations importe peu et on n'a pas besoin d'éviter les références FORWARD.
Les tableaux SIMULA sont dynamiques et les bornes peuvent être des expressions quelconques qui
sont évaluées à l'entrée du bloc où le tableau est déclaré.

PASCAL:
A,B : ARRAY [1..10,2..5] OF REAL;
SIMULA:
REAL ARRAY A,B (1:10,2:5);
Mais aussi:
BEGIN
INTEGER N; ..... N:=10;
BEGIN
INTEGER ARRAY A (1:N);
INTEGER ARRAY B (1+ININT : 2*N);
...
END
END
Par contre, les index sont limites a des valeurs entieres et l'affectation entre tableaux ( A:= B ) n'est
pas permise.
Le seul constructeur de type, CLASS, declare des types de structures dynamiques. On montre ici
une application simple mais nous verrons que le concept de CLASS fait la force de SIMULA.

SIMULA PASCAL
CLASS COMPLEX; TYPE
BEGIN COMPLEX= STRUCT
REAL R,I; R,I: REAL;
END; END;
VAR
REF(COMPLEX) C; C: ^COMPLEX;
... ...
C:- NEW COMPLEX; NEW (C);

Nous verrons les classes en plus de détails plus tard ainsi que la déclaration de procédures.
Les étiquettes SIMULA sont des identificateurs déclarés implicitement par leur apparition devant
un énoncé:
L: ;........; GOTO L;

4.1. Enoncés
L’affectation est de la forme:
I:= I+1;
mais aussi,
I:= J:= K:= I+1;

Pour les pointeurs on utilise


":-" : P1 :- P2;
IF:
if C then <enonce autre que IF> else <enonce>

Note: on permet aussi des expressions conditionnelles:


I := if A>B then I+1 else I-1;
BLOC:
begin <possiblement des declarations>;
<suite d'enonces >
end
GOTO:
TOTO:......; goto TOTO;
WHILE:
while I<10 do I:=I+1;
33
Langages de Programmation Parallèle, Java & Simula.

FOR:
En gros il y a la boucle avec un pas (variable) et celle avec une liste de valeurs. Avec une liste, la
variable de contrôle peut être de type arithmétique, caractère, texte, booléens, ou pointeur.
for I:= 1 step I+1 until 100 do ...;
for I:= 100 step -1 until 1 do ...;
for I:= 1,I+3 while I**2 < 100 do ...;
for I:= 2,3,5,7,11 step 4 until 51 do ...;
for C:= 'A','E','I','O','U','Y' do ... ;
for P:- P1,P2,P3 do ...;

4.2. Textes
Les chaînes de caractères en SIMULA sont gérées dynamiquement et les procédures d'accès et de
traitement des chaînes favorisent le traitement séquentiel des caractères. On remarquera donc une
analogie aux entre/sorties. A chaque chaîne est associe un descripteur contenant l'adresse de la
chaîne, sa longueur et l'index du prochain caractère a traiter. Une variable de type TEXT désigne ce
descripteur. Initialement, les variables de texte désignent NOTEXT, la chaîne vide (équivalent a
NONE).
La création de chaînes se fait avec une de deux fonctions:
BLANKS(N) : qui crée une chaîne de N caractères blancs
COPY (T) : qui crée une copie de T.

TEXT T1,T2;
...
T1 :- BLANKS(80);
T2 :- COPY("** IFT 3040 **");
Les opérateurs d'affectation et de comparaison peuvent porter sur les descripteurs ( :-, ==, =/= ) ou
sur le contenu des chaînes ( :=, =, NE ). Voici l’équivalent PASCAL ou on aurait
T1,T2 : ^ ALFA .

SIMULA PASCAL
T1:- T2; T1 := T2;
T1:= T2; T1^:= T2^;

T1 == T2 T1 = T2
T1 NE T2 T1^ <> T2^
On peut aussi tester l'ordre lexicographique de chaînes avec >,>=,< et <=.
Opérateur de concaténation de chaîne: &
text T;
...
outtext( "Error:" & T & "in system") ;
La manipulation du contenu de chaînes se fait a l'aide de procédures et fonctions et la notation (que
nous reverrons avec les CLASS) est celle utilisée en PASCAL pour l'accès a des champs de
STRUCT: la notation de point.
T.LENGTH:
donne la longueur de la chaîne T
T.POS:
donne l'index du prochain caractère à traiter
T.MORE:
TRUE si T.POS > T.LENGTH, FALSE autrement
T.GETCHAR:
retourne prochain caractère et incrémente T.POS
T.PUTCHAR(C):
place le caractère C a l'endroit POS et incrémente POS.
Remarquer la ressemblance avec INCHAR et OUTCHAR.
T.SETPOS(N):
donne la valeur N a T.POS
T.GETINT et T.PUTINT(I):
34
Langages de Programmation Parallèle, Java & Simula.

à peu près équivalent a ININT et OUTINT


T.SUB(I,N):
désigne une sous chaîne de T de longueur N débutant a la position I. Remarquer qu'on ne cree pas
de copie. SUB est utile pour avoir accès au champs de record formates.
T.STRIP:
retourne la sous chaîne T.SUB(1,N) ou tous les caractères après le n-ieme sont blancs.
5. Procédures
Par défaut, les paramètres simples sont passes par valeur et les tableaux sont passes par référence.
On peut modifier ceci avec les listes VALUE et NAME (noter que le NAME d'ALGOL est plus
puissant que le VAR de PASCAL).
PASCAL:
PROCEDURE ADD (A,B: INTEGER; VAR C:INTEGER);
BEGIN
C:= A+B;
END;

FUNCTION SUM (A,B:INTEGER) : INTEGER;


BEGIN SUM:= A+B END;

SIMULA:
PROCEDURE ADD (A,B,C);
NAME C; INTEGER A,B,C;
BEGIN
C:= A+B;
END;

INTEGER PROCEDURE SUM (A,B); INTEGER A,B;


SUM:= A+B;

L'utilisation dans les deux cas serait:


ADD (1, SUM(I,J), K);
Le corps d'une procédure est un seul énoncé (voir SUM). Assez souvent cet énoncé est un bloc (voir
ADD). Quand on passe un tableau, on ne doit pas spécifier les bornes:
PROCEDURE MATMUL (A,B,C) ; REAL ARRAY A,B,C ;
Voici maintenant plus de détails. En SIMULA, il y a 3 modes de passage de paramètres:
par VALUE : comme en PASCAL
par REFerence: à peu près comme le VAR de PASCAL
par NAME : comme en ALGOL 60
Le passage par nom (NAME) est équivalent a un remplacement textuel de chaque occurrence d'un
paramètre formel (PF) par le texte du paramètre actuel (PA) correspondant. Chaque référence a un
PF implique donc une re-évaluation du PA. Avec le passage par valeur ou par référence, il existe
pour chaque PF une variable locale initialisée selon la valeur du PA calculée une fois a l'appel de la
procédure (ou création d'objet).
Par défaut, les variables simples (int,real,bool et char) sont passées par valeur et les autres par
REFerence. Le mode par défaut pour un paramètre peut être change en mettant l'identificateur du
paramètre dans des listes NAME ou VALUE avant la spécification du type des paramètres.
Pour les classes, NAME ne peut pas être employé comme mode de passage. On ne peut pas non
plus passer des procedures, etiquettes ou switch. Ceci évite la possibilité de référence a des
variables locales de blocs disparus.
Voici 2 tableaux qui résument les modes de passage possibles. Un "D" indique le mode par Défaut,
un "O" indique un mode Optionnel obtenu avec une spécification NAME ou VALUE, et "X"
indique un mode impossible ou défendu.

35
Langages de Programmation Parallèle, Java & Simula.

Paramètres de procédures

mode de passage
type de paramètre
VALUE REF NAME
- real,int,bool ou char D X O
- text ou array de
O D O
(real,int,bool,char)
- ref(..) ou array de (ref,text) X D O
- procedure,label ou switch X D O

Paramètres de Classe

mode de passage
type de paramètre
VALUE REF
- real,int,bool ou char D X
- text ou array de
O D
(real,int,bool,char)
- ref(..) ou array de (ref,text) X D

Ces tableaux semblent à première vue un peu compliques sans raison. Par exemple, pourquoi ne
peut-on pas passer une REFerence par VALUE ou quelle est la différence entre le passage d'un
texte par VALUE ou par REFerence? La réponse est que la notion de passage par valeur, dans le
cas d'une structure de liste ou de structures pointées, est quelque peu ambiguë. Le passage par
valeur est suppose empêcher les effets de bords en créant une copie locale du PA. En fait le passage
par value d'un pointeur en PASCAL protège efficacement la variable pointeur passée comme
paramètre; mais le contenu de l'objet pointe n'est nullement protège. Dans ce cas, un VRAI passage
par valeur exigerait une copie complète non seulement de l'objet pointe mais aussi de tout autre
objet atteignable par des pointeurs dans cet objet. Pour les pointeurs (variables REF), ce qui serait
appelé en PASCAL le passage par valeur est appelé en SIMULA passage par référence et le passage
par VALUE est interdit, C.A.D. le pointeur utilise en paramètre est protége, mais la structure
pointée ne l'est pas.
A peu près la même chose s'applique au TEXTs. Une variable TEXT en SIMULA est un
descripteur qui contient, entre autre, un pointeur vers une chaîne de caractères et un indicateur,
POS, de l'endroit ou on est rendu dans le traitement de cette chaîne. Dans ce cas, le passage par
défaut (mode REF) implique une copie locale du descripteur: le descripteur (PA) est donc protège
mais la chaîne elle même peut être changée. Pour le passage par VALUE, on fait une copie et de la
chaîne et du descripteur (avec POS remis à 0).

6. Aspects avancés
6.1. Classes et objets
La puissance de SIMULA réside dans son concept de CLASS: la spécification d'un ensemble
d'objets du même type..
Une déclaration de CLASS définit un type d'OBJET dynamique. Ces objets ressemblent au
RECORDs de PASCAL dans le sens qu'ils ont des attributs (champs) accessibles par notation
pointée (ex: P.A ) et ils ressemblent aux procédures dans le sens qu'une CLASS peut avoir des
énoncés et des paramètres. En plus, la PREFIXATION permet le traitement rationnel d'objets avec
variantes et les objets peuvent fonctionner en COROUTINES. Finalement, SIMULA gère
l'allocation dynamique de façon très sécuritaire en initialisant tout pointeur a NONE et en
récupérant automatiquement l'espace occupe par des objets inaccessibles.

36
Langages de Programmation Parallèle, Java & Simula.

Prenons un premier exemple graphique. Un POINT est défini par ses coordonnées rectangulaires X
et Y. Un autre attribut est sa distance DIST de l'origine. On peut déclarer le type POINT avec
CLASS POINT;
BEGIN
REAL X,Y,DIST;
END;
Les objets spécifies par des classes doivent être créées dynamiquement par l'opérateur NEW et on
les accède par des pointeurs (REFERENCEs).
REF(POINT) P;
...
P:- NEW POINT;
P.X:= 1; P.Y:= 3.0;
P.DIST:= SQRT (P.X**2 + P.Y**2);
Voyons l'analogie avec les procédures avec une autre version de POINT. Ici on met X et Y en
position paramètres pour exiger une valeur initiale à la création et on met un énoncé pour calculer la
valeur de DIST.
CLASS POINT (X,Y); REAL X,Y;
BEGIN
REAL DIST;

DIST := SQRT (X**2+Y**2);


END;
P :- NEW POINT (1.0,3.0);

A la création, X et Y reçoivent les valeurs 1 et 3 et le corps de POINT est exécuté avant de revenir à
l'énoncé de création. POINT fonctionne comme une procédure à la différence que le "bloc
d'activation" n'est pas détruit au retour et qu'on peut accéder au variables locales par le pointeur:
P.X, P.Y et P.DIST .
Les attributs accessibles d'un objet ne sont pas limites a des variables; on peut aussi avoir des
procédures et fonctions locales. Ceci permet d'intégrer les opérations permises sur un type d'objet à
la déclaration de sa classe. Ici l'attribut DIST dépend des valeurs X et Y et un changement de X ou
Y entraîne un changement de DIST. Il est plus élégant de déclarer DIST comme attribut fonctionnel
qui calcule la bonne valeur chaque fois qu'on en a besoin:
CLASS POINT (X,Y); REAL X,Y;
BEGIN
REAL PROCEDURE DIST;
BEGIN DIST:= SQRT(X**2 + Y**2) END;
END;
La distance du point P est donnée par P.DIST et remarquer que DIST étant une fonction on ne peut
pas lui affecter une valeur avec P.DIST:=5 .
6.2. Simset
Nous avons vu que la préfixation d'une classe par le nom d'une autre lui ajoutait tous les attributs,
déclarations et énoncés du préfixe. La préfixation s'applique aussi au BLOCs et aux programmes.
Par ce biais, SIMULA permet une extension du langage. SIMULA à deux classes prédéfinies,
SIMSET et SIMULATION, que l'usager peut utiliser comme préfixe. SIMSET contient des
déclarations utiles pour le traitement de listes et SIMULATION sert a la simulation. Les listes de
SIMSET sont circulaires à deux sens avec têtes de listes. Trois classes sont définies: HEAD qui
décrit les têtes de listes et comprend les procédures et fonctions s'appliquant aux listes, par exemple
FIRST pour donner le premier élément d'une liste; LINK que l'usager utilise comme préfixe aux
objets qui seront membres de listes; et LINKAGE, préfixe commun a HEAD et LINK afin de
permettre le chaînage. Voici le squelette de SIMSET.
CLASS SIMSET;
BEGIN
CLASS LINKAGE;
BEGIN
REF(LINK) PROC SUC ; ...;
REF(LINK) PROC PRED; ...;
REF(LINKAGE) PROC PREV; ...;
END;

37
Langages de Programmation Parallèle, Java & Simula.

LINKAGE CLASS HEAD;


BEGIN
REF(LINK) PROC FIRST ; ...;
REF(LINK) PROC LAST ; ...;
BOOLEAN PROC EMPTY ; ...;
INTEGER PROC CARDINAL;..;
PROC CLEAR ; ...;
END;

LINKAGE CLASS LINK;


BEGIN
PROC OUT ;...
PROC FOLLOW (X); REF(LINKAGE) X; ...
PROC PRECEDE(X); REF(LINKAGE) X; ...
PROC INTO (L); REF(HEAD) L ; ...
END;
END;
Les noms sont assez évocateurs des opérations implantées. Remarquons qu'un LINK ne peut être
que dans une seule liste a la fois. Un objet dans une liste qui est inséré dans une autre en sera retire
au préalable. En général, une opération sans sens comme mettre un objet devant un autre qui est soit
NONE soit hors d'une liste ne donne pas d'erreur; l'opération n'a pas d'effet.
Soit REF(HEAD) L et REF(LINK) A,B,
L.EMPTY VRAI si L est vide
L.FIRST donne le premier élément de la liste L (ou NONE)
L.LAST donne le dernier élément
L.CARDINAL donne le nombre d'éléments dans L
L.CLEAR vide la liste
A.SUC donne le successeur de A s'il existe, mais L.LAST.SUC donne NONE
A.PRED donne le prédécesseur (L.FIRST.PRED==NONE)
A.PREV donne le prédécesseur qu'il soit HEAD OU LINK
A.OUT sort A de sa liste s'il y est
A.FOLLOW(B) met A derrière B
A.PRECEDE(B) met A devant B
A.INTO(L) met A en queue de la liste L
Voici un exemple d'un programme qui utilise SIMSET pour imprimer a l'envers tous les caractères
non blancs sur INPUT.
SIMSET BEGIN

LINK CLASS ELEMENT (C);


CHARACTER C; ;

REF(HEAD) LISTE;
REF(ELEMENT) P;

LISTE :- NEW HEAD;

WHILE NOT LASTITEM DO


NEW ELEMENT( INCHAR ) . INTO (LISTE);

P:- LISTE.LAST;
WHILE P=/= NONE DO
BEGIN
OUTCHAR(P.C); P:-P.PRED;
END;
END

6.3. Coroutines
Les objets de SIMULA fonctionnent comme des co-routines. Pour comprendre les co-routines,
regardons d'abord comment marche l'appel de procédure. Quand une procédure P1 appelle une
procédure P2: 1) une copie de P2 est créée, 2) le contrôle passe au début de P2, 3) P2 exécute
jusqu'a la fin, 4) on détruit P2 et 5) le contrôle revient a P1. Il existe une relation maître-esclave
entre P1 et P2.
38
Langages de Programmation Parallèle, Java & Simula.

Avec deux coroutines P1 et P2, le passage de contrôle se fait d'égal à égal. Pour passer le contrôle à
P2, P1 fera RESUME (P2). Pour revenir a P1, P2 fera RESUME(P1). Noter qu'une coroutine
activée par RESUME continue la ou elle était quand elle a passe le contrôle a quelqu'un d'autre, pas
au début. Noter aussi qu'un "retour" n'implique pas la destruction de la routine.
La situation est légèrement compliquée par le problème de création et de fin des routines. Quand
une coroutine passe à travers son END final, à qui passer le contrôle? Pour expliquer ceci, on
suppose que chaque coroutine a un pointeur cache RETOUR qui peut designer soit une autre objet,
soit le bloc englobant ou se trouve sa déclaration. Un objet passe le contrôle a RETOUR avec
l'instruction DETACH qui a l'effet suivant:
temp :- RETOUR;
RETOUR:- "bloc englobant";
RESUME (temp);
Notons que la fin d'une coroutine est équivalente a un DETACH.
Maintenant voyons la création. Quand P1 exécute
P2 :- NEW C;

ceci est équivalent a


temp :- new C ;
temp.RETOUR :- P1;
RESUME (temp);
P2:- temp;
Dans les exemples que nous avons vu, les objets créés ne faisaient ni RESUME ni DETACH mais
exécutaient jusqu'au END final et le retour était effectué à l'énoncé de création. Noter que dans un
objet qui fait plusieurs DETACH, le premier rend le contrôle au créateur mais les autres rendront le
contrôle au bloc englobant, normalement le programme principal.
La dernière instruction de contrôle entre coroutines est CALL. Utilisée avec DETACH cette
instruction permet le contrôle procédural entre coroutines. C'est a dire, une coroutine peut retourner
à celle qui l'a activée sans la nommer explicitement. L'effet de CALL(P2) par P1 est le suivant:
P2.RETOUR:- P1; RESUME(P2);

On remarque que l'exécution de DETACH par P2 rend le contrôle ˆ P1.


Utilisées de façon disciplinée, les instructions RESUME, CALL et DETACH permettent souvent
une structuration souple et élégante de programmes complexes. Par contre comme avec les GOTO,
on peut facilement créer la pagaille. Il sera donc souvent préférable d'utiliser les procédures de
contrôle implantées dans la classe SIMULATION (à partir de RESUME etc... évidemment).
Voici un exemple d'un jeu avec N joueurs. Chacun, à son tour, roule un de et cumule les points. Le
premier arrivé a 100 gagne.

BEGIN
INTEGER U,N;
U:= 12345;
N:= ININT;

BEGIN
REF(J) ARRAY JOUEUR (1:N);
INTEGER I;

CLASS J (MOI); INTEGER MOI;


BEGIN
INTEGER SUIVANT, SUM;

SUIVANT := MOD(MOI,N)+1;
DETACH;
WHILE SUM < 100 DO BEGIN
SUM := SUM + RANDINT (1,6,U);
RESUME(JOUEUR(SUIVANT));
END;
OUTINT(MOI,5); OUTTEXT(" A GAGNE"); OUTIMAGE;
END;

FOR I:= 1 STEP 1 UNTIL N DO JOUEUR(I) :- NEW J (I);

39
Langages de Programmation Parallèle, Java & Simula.
RESUME(JOUEUR(1));
OUTTEXT("-- FIN DU JEU --");
END;
END

7. Aspect parallèle
7.1. Simulation
SIMULATION est une classe standard qui introduit le concept de PROCESS afin de décrire des
objets dont la synchronisation est basée sur l'évolution du temps. Avec cette class comme préfixe,
l'usager n'utilise plus les primitives RESUME, DETACH et CALL qui font des transferts de contrôle
explicites. Par contre, des procédures de plus haut niveau sont fournies pour qu'un PROCESS qui
veut s'arrêter temporairement puisse indiquer l'heure (EVTIME) à laquelle il désire continuer. Tous
les PROCESS actifs sont chaînes dans un échéancier SQS par ordre croissant de EVTIME et l'arrêt
d'un process entraîne l'activation automatique du process en tête de SQS. (Noter que le "temps" est
simulé et n'a aucun rapport avec l'écoulement réel du temps de calcul; les evtimes servent à imposer
une relation d'ordre entre le phases de calcul des différents process). Voici le squelette de la classe
SIMULATION.
SIMSET CLASS SIMULATION;
BEGIN
LINK CLASS PROCESS;
BEGIN
BOOL PROC TERMINATED;...
BOOL PROC IDLE ;...
REAL PROC EVTIME ;...
REF(PROCESS) PROC NEXTEV;...

DETACH;....
END;

{ REF(HEAD) SQS ; }
REF(PROCESS) PROC CURRENT;...
REAL PROC TIME ;...
REF(PROCESS) MAIN ;

PROC HOLD (DT); REAL DT;...


PROC PASSIVATE ;...
PROC WAIT (L); REF(HEAD) L;...
PROC CANCEL(P); REF(PROCESS) P;...

END;

Notes:

• SIMULATION est préfixé par SIMSET pour donner accès au traitement de listes.
• Un PROCESS est préfixé par LINK. Le chaînage dans SQS étant indirect, un process peut
être à la fois dans SQS et une seule autre liste.
• Pour raison de sécurité, la liste SQS est inaccessible à l'usager. Par contre CURRENT est une
fonction qui retourne une référence sur le premier process de SQS. Par définition, ce premier
process est celui qui est en exécution.
• TIME retourne CURRENT.EVTIME, c.a.d. l'heure courante.
• Le programme peut agir comme un process et il est designé par MAIN.

Voyons les attributs de PROCESS. Soit REF(PROCESS) P,


P.IDLE : TRUE si P est hors de SQS
P.TERMINATED: TRUE si P a passe son END final
P.EVTIME : erreur si P.IDLE, l'heure de reactivation
de P autrement
P.NEXTEV : successeur de P dans SQS ou NONE.

40
Langages de Programmation Parallèle, Java & Simula.

Le premier énoncé de PROCESS est DETACH. La création d'un process ne le fait donc pas
démarrer. SIMULATION contient aussi un certain nombre de procédures de "scheduling" qui
manipulent les process dans SQS. Ces procédures opèrent par défaut sur CURRENT et toutes se
terminent en passant le contrôle au premier process de SQS (en général un nouveau CURRENT).

HOLD (DT); ajoute DT à current.evtime et déplace current en conséquence


PASSIVATE; enlève CURRENT de SQS
CANCEL(P); enlève P de SQS
WAIT(L); équivalent à: CURRENT.INTO(L); PASSIVATE;
Il existe aussi 2 énoncés de scheduling, REACTIVATE et
ACTIVATE. Ces énoncés ont plusieurs options. Soit
REF(PROCESS) P,P1 :
REACTIVATE P AT T; P est cédulé (placé dans SQS) pour l'heure T
REACTIVATE P DELAY DT; P est cédulé pour l'heure TIME+DT
REACTIVATE P BEFORE P2;
P est cédulé devant ou après P2 avec la même heure que P2
REACTIVATE P AFTER P2;
REACTIVATE P; P est placé devant current et prend le contrôle immédiatement

Avec les options AT et DELAY, P est placé derrière tout autre process cédulé pour la même heure
(FIFO). Cependant, on peut demander a le mettre devant les autres avec le mot clé PRIOR:
REACTIVATE P DELAY 10 PRIOR;
Noter que les trois énoncés suivants ont tous le même effet:
REACTIVATE P DELAY 0 PRIOR;
REACTIVATE P;
REACTIVATE P BEFORE CURRENT;
Les cas bizarres, P==NONE, P2==NONE, P2.IDLE, P==P2 etc... ne produisent aucun effet.
Le deuxième énoncé d'activation ACTIVATE est identique a REACTIVATE si P.IDLE est vrai (P
n'est pas dans SQS), autrement si P est déjà dans SQS, ACTIVATE n'a aucun effet et P reste ou il
était.
Voici un programme qui simule une file d'attente avec des PROCESS client. Pour montrer
l'évolution de la taille de la queue, on utilise un process reporter qui imprime le nombre de
personnes dans le système à toute les 10 unités de temps. La simulation dure 200 unités de temps.
L'exemple montre aussi l'emploi de procédures de traitement de listes: into, out et empty.
! Exemple de la simulation d'une file d'attente ;
Simulation begin
ref(head) Q;
INteger U, N;
Boolean Guichet_Occupe;

process class client;


begin
activate new client delay negexp(1/10, U);

N := N+1;
if Guichet_occupe then begin
into(Q);
passivate;
out;
end;

Guichet_occupe := TRUE;
hold( uniform( 0,18, U ) );
Guichet_occupe := FALSE;

if not Q.empty then activate Q.first;


N := N-1;
end;

process class reporter;


while TRUE do

41
Langages de Programmation Parallèle, Java & Simula.
begin integer i;
outint(time, -6);
for i := 1 step 1 until N do outchar('*');
outimage;
hold(10);
end;

Q :- new Head;
U := clocktime;

activate new reporter delay 0 ;


activate new client delay randint(0,5,U) ;

hold(200);
outtext("=== Fin de la simulation ===");
end

7.2. Parallélisme
Les processus de SIMULA travaillent en quasi-parallèle: a tout moment, un seul est actif. De plus,
un processus qui n'appelle pas de procédure ou d'énoncé de scheduling est ininterruptible et TIME
reste fixe durant son exécution. Pour modéliser le fait que le travail d'un processus prend un certain
temps et que d'autres travaillent en parallèle, il faut insérer des énoncés HOLD(...) aux endroits
appropries ou ... représente la "durée" des actions du processus.
L'exemple suivant démontre les problèmes de synchronisation dans les systèmes d'exploitation. On
simule 5 processus qui incrémentent chacun la variable globale N 10 fois. On s'attendrait a ce que N
vaille 50 à la fin de la simulation. En fait, à cause du délai entre l'accès et la mise a jour de N, cette
valeur sera moindre. On simule ce délai avec une procédure TRANSFER qui fait un HOLD
aléatoire et on insère un HOLD dans le processus.
simulation begin
integer i,n,u;
integer procedure transfer(i); integer i;
begin
transfer := i;
hold(uniform(0,2,u));
end;

process class proc;


begin integer k;
for k:= 1 step 1 until 10 do
begin
n:= transfer(n+1);
hold(1);
end;
end;

u:= 47;
for i:= 1 step 1 until 5 do
activate new proc;
outtext("n devrait valoir 50"); outimage;
hold(10000);
outtext("n="); outint(n,5); outimage;
end;
L'exécution donne
N DEVRAIT VALOIR 50
N= 14

42
Langages de Programmation Parallèle, Java & Simula.

Conclusion
Nous avons présenté deux langages de programmation orientés objets qui représentent presque deux
bornes historiques en ce domaine ; Simula le plus ancien -et qui fait encore ces preuves surtout sur
le plan pédagogique-, Java le plus récent et qui fait la une des langages du troisième millénaire
orienté réseau. La puissance du concept d’objet introduit par les deux langages, leur permet de
concevoir et produire des applications parallèles par excellence. Alors que Simula utilise la notion
de simulation, d’événement et de gestion de processus ; Java, qui prétend être multi-usage et
portable, introduit le concept de thread (processus léger) et le mécanisme de moniteur et de
synchronisation.

En effet, la modélisation des processus concurrents se fait avec divers modèles comme la théorie
des files d’attente, les réseaux de Petri ou les compteurs abstraits. Cette notion de processus a
évolué pour faire apparaître un grain d’activité plus fin, le processus léger qui introduit de la
concurrence à l’intérieur d’un acteur doté d’un espace d’adressage. Elle devrait évoluer pour
exprimer une séquence d’actions qui se déroulent sur divers sites d’un système réparti et qui sont
mises en relation par des messages.
Nous pensons qu’à travers la panoplie d’exemples que nous avons exposés, le lecteur a eu une idée
claire et assez fine sur les deux langages. Ceci n’exclue la nécessité de pratiquer la programmation
en codant lui même ses propres chefs-d’œuvre.

Bibliographie
1. Birtwistle, G.M., O.-J. Dahl, B. Myhrhaug and K. Nygaard. "SIMULA Begin." p. 391,
AUERBACH Publishers Inc, 1973.

2. Dietel & Deitel, Java How to Program, Fourth Edition, Prentice Hall 2002.

3. Kostrzewa Bruno, Java-TD, 2001,


http://b.kostrzewa.free.fr/java

4. Laura Lemay & Rogers Cadenhead, Le Magnum : Java2, CampusPress 2001.

5. Jean G. Vaucher, Introduction à Simula,


http://www.iro.umontreal.ca/~simula/Standard/intro.html

43