[Go to site: main page, start]

0% ont trouvé ce document utile (0 vote)
47 vues51 pages

Exécution de Threads en Java

Ce document décrit les concepts de base des threads et des processus en Java. Il explique les classes Runnable et Thread qui permettent de créer et gérer des threads, ainsi que la classe Process pour les processus externes. Les différents états d'un thread sont également présentés.

Transféré par

sam
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd
0% ont trouvé ce document utile (0 vote)
47 vues51 pages

Exécution de Threads en Java

Ce document décrit les concepts de base des threads et des processus en Java. Il explique les classes Runnable et Thread qui permettent de créer et gérer des threads, ainsi que la classe Process pour les processus externes. Les différents états d'un thread sont également présentés.

Transféré par

sam
Copyright
© All Rights Reserved
Nous prenons très au sérieux les droits relatifs au contenu. Si vous pensez qu’il s’agit de votre contenu, signalez une atteinte au droit d’auteur ici.
Formats disponibles
Téléchargez aux formats PDF, TXT ou lisez en ligne sur Scribd

Programmation Réseau

Système
d’exécution Java

[Link]@[Link]
!
UFR Informatique
!
2013-2014
API Java

Les Threads
• on rappelle qu’un processus est un programme (de
nature statique) en cours d’exécution (de nature
dynamique)

• son exécution nécessite un environnement


(espace d’adressage, objets E/S, etc.)

• un thread est un fil d’exécution dans un processus


donné

• un processus peut être multi-threadé (mais au


minimum mono-threadé)
2
API Java

• un fil d’exécution est distinct des autres et a pour


attributs :

• un point courant d’exécution (pointeur


d’instruction, ou PC program counter)

• une pile d’exécution (stack)

• on notera qu’un Thread partage tout le reste de


l’environnement avec les autres Threads qui lui
sont concurrents dans le même processus

3
API Java

• La JVM de Java est multi-threadé et offre au


programmeur la possibilité de gérer des threads à
sa guise

• il n’est pas précisé comment ces threads sont pris


en charge par le système sous-jacent

• On notera au passage que Java permet aussi de


manipuler des processus (lesquels sont pris en
charge par le système)

• il n’y a pas de notion processus dans Java lui-


même,un processus est un objet du système hôte
4
API Java

L’environnement d’exécution en Java


• L’environnement d’exécution d’une JVM est
disponible dans la JVM elle-même sous la forme d’un
objet de type [Link]

• attention, il n’existe qu’un seul exemplaire d’un tel


objet (Singleton); impossible de créer un objet de
cette classe

• L’instance unique peut être retrouvée par appel à la


méthode statique

Runtime getRuntime();
5
API Java

• de nombreuses méthodes sont disponibles dans la


classe Runtime

• entre autres celles permettant de demander au


système hôte de créer un processus concurrent
(l’exécution d’un programme en dehors de la JVM
elle-même)

• c’est la famille des méthodes

Process exec(…);

• voir la documentation…
6
API Java

Les processus en Java


• les processus dont l’exécution (externe) a été
commandée par une JVM sont représentés dans
celle-ci sous la forme d’objet [Link]

• ces objets permettent de

• communiquer avec les processus externes


correspondants

• se synchroniser avec leur exécution

7
API Java

• on peut obtenir les flux d’entrée et sorties du


processus externe avec :

InputStream getErrorStream();
InputStream getOutputStream();
OutputStream getIntputStream();

• une entrée du processus correspond à une sortie


depuis la JVM et vice-versa

Process
in err
out
Processus
JVM
8
API Java

• il est possible de se synchroniser avec l’exécution du


processus externe :

int exitValue();
int waitFor();

• qui renvoient la valeur de terminaison du processus


externe, en mode bloquant avec waitFor() et non-
bloquant avec exitValue()

9
API Java

Les threads en Java


• le mécanisme est plus complexe que celui des
processus car il est interne au système Java

• il repose sur deux types d’objets

• les Runnables, qui représentent des objets


contenant du code qui servira de code principal
(équivalent du main pour les threads); la nature
statique d’une exécution

• les Threads, qui représentent l’exécution elle-


même; la nature dynamique d’une exécution
10
API Java

• à tout Thread doit être associé un Runnable

• le point d’entrée du code qu’il exécutera…

• bien entendu, à un Runnable donné il est possible


d’attacher autant de Threads que l’on désire

11
API Java

[Link]
• il s’agit d’une interface qui ne déclare qu’une seule
méthode à implémenter :

void run()

• lorsqu’un Thread démarrera, son exécution


• débutera par un appel à la méthode run du
Runnable qui lui est attaché

• terminera lorsque cet appel initial terminera


12
API Java

[Link]
• les Threads Java ont plusieurs attributs

• String name [rw] : son nom

• long id [ro] : son identité

• int priority [rw] : sa priorité (les Threads


sont ordonnancés à l’aide de cette priorité)

• boolean daemon [rw] : son mode d’exécution


(démon ou non, voir plus loin)

13
API Java

• [Link] state [ro] : son état parmi

• NEW, RUNNABLE, BLOCKED, WAITING,


TIMED_WAITING, TERMINATED

• sa pile (mais dont on ne peut qu’observer la trace)

• son groupe de Thread

• quelques autres attributs mineurs…

14
API Java

• les états possibles d’un thread et l’automate de


transition sont :

[Link]
15
API Java

• Point sur la terminaison d’une JVM

• on indique généralement qu’un programme Java


s’arrête lorsqu’on sort du main

• un programme ne s’arrête pas, seul un processus


s’arrête (abus de langage très commun)

• mais surtout il ne suffit pas sortir du main,


encore faut-il sortir du premier appel au main (il
est possible de faire des appels récursifs à main)

• mais en plus, il faut attendre que TOUS les


Threads qui ne sont pas démons s’arrêtent
16
API Java

• il existe au moins un Thread démon :

• le garbage collector…

• il en existe souvent un autre :

• le Thread de l’interface graphique…

17
API Java

• le contrôle de l’exécution d’un Thread s’effectue à


travers les méthodes suivantes :

• void start(), qui permet de démarrer le


Thread (il va alors appeler la méthode run() du
Runnable associé)

• void join(), qui permet d’attendre que le


Thread s’arrête

18
API Java

• void interrupt(), qui permet de positionner


le statut du Thread à interrompu
• ce qui n’a aucun effet immédiat direct, sauf
terminer certains appels en cours ou permettre
au Thread concerné de savoir qu’un autre
Thread souhaite l’interrompre

• Important : il n’existe pas d’autre technique pour


arrêter un Thread que de se débrouiller pour qu’il
sorte de son premier appel à run()…

19
API Java

• la classe Thread possède plusieurs méthodes


statiques :

• Thread currentThread(), qui permet de


récupérer l’objet Thread courant (le « this » du
système de Thread)

• boolean interrupted(), qui permet de


déterminer si le Thread courant a reçu une
demande d’interruption

20
API Java

• void sleep(long ms)


void sleep(long ms,long ns)
qui permettent au Thread courant de renoncer à
son exécution pour la durée exprimée (au
minimum)

• void yield(), qui permet au Thread courant


de renoncer à la suite de son quota d’exécution et
de reprendre une place dans l’ordonnanceur

21
API Java

• la classe Thread possède plusieurs constructeurs,


dont les plus utiles permettent de lui associer un
Runnable

• les deux constructeurs les plus fréquemment


employés sont :

• Thread(Runnable)
• Thread(Runnable,String)

22
API Java

class MyCode implements Runnable {


public void run() {
int N = (int)([Link]()*5);
for (int i=0; i<10000*N; i++) {
[Link](
[Link]().getName()
+" i="+i);
}
}
}

23
API Java

public class Example {


public static final int N = 20;
public static void main(String []args) {
MyCode code = new MyCode();
Thread []t = new Thread[N];
// création des N threads
for (int i=0; i<N; i++) t[i]=new Thread(code,"T"+i);
// lancement des N threads
for (int i=0; i<N; i++) t[i].start();
// attente de la terminaison des N threads…
for (int i=N-1; i>=0; i--) {
try {
t[i].join();
} catch(InterruptedException e) {}
[Link]("Terminaison de "+t[i].getName());
}
[Link]("tout est fini…");
}
}

24
API Java

• il est à noter qu’un Thread implémente l’interface


Runnable par une méthode run() qui ne fait rien

• ainsi on peut dériver la classe Thread et redéfinir


la méthode run() afin d’obtenir un objet Thread
qui exécute un code qu’il contient lui-même

25
API Java

Le(s) problème(s) de la concurrence…


class Compteur {
private int valeur;
public Compteur() { valeur = 0; }
public int getValeur() { return valeur; }
public void setValeur(int v) { valeur=v; }
}
!
class MyCode implements Runnable {
private Compteur c;
public MyCode(Compteur c) { this.c = c; }
public void run() {
for (int i=0; i<100000000; i++) {
[Link]([Link]()+1);
}
}
}
26
API Java

public class Example {


public static final int N = 20;
public static void main(String []args) {
Compteur c = new Compteur();
MyCode code = new MyCode(c);
Thread []t = new Thread[N];
for (int i=0; i<N; i++)
t[i] = new Thread(code,"T["+i+"]");
for (int i=0; i<N; i++) t[i].start();
for (int i=0; i<N; i++) {
try {
t[i].join();
} catch(InterruptedException e) {}
}
[Link]("tout est fini…");
[Link]("Le compteur est égal à "+
[Link]());
}
}

27
API Java

00 ! ! !
000. 0
x100.
≠ 20
1. 363
716. 79
1.

28
API Java

• le problème est la non-atomicité de l’opération d’incrémentation


du compteur

• le compteur est une ressource à laquelle les Threads


accèdent concurremment

• Ti prend la valeur disons v


• Ti calcule v+1
• Ti passe la main à Ti+1
• Ti+1 prend la valeur (c’est encore v)
• Ti+1 calcule v+1
• Ti+1 range la valeur v+1
• Ti+1 passe la main à Ti
• Ti range la valeur v+1 (à ce point les deux ont bien
incrémenté chacun une fois, le malheur est qu’ils ne se
sont pas entendus pour le faire correctement)
29
API Java

• Principe en programmation concurrente :

• on ne doit jamais faire aucune supposition sur


l’ordonnancement des exécutions

• par conséquent, il faut être offensif :

• prendre des précautions afin d’éviter les


problèmes

• défensif : attendre que le problème arrive pour


le corriger

30
API Java

• dans le cas précédent (celui du compteur) la solution


est, par exemple, de rendre atomique la suite
d’opérations concernée

• on veut éviter de ne faire qu’une partie de la suite


d’opérations : prendre la valeur, ajouter un, stocker
le résultat

• une telle suite d’opération est appelée une


section critique

• on souhaite donc contrôler quels sont les


Threads autorisés à entrer dans la section
critique (penser à un sas, un portillon de contrôle)
31
API Java

• le mécanisme usuel permettant de contrôler


l’entrée d’une section critique est le verrou qui
assure une propriété d’exclusion mutuelle

• un verrou est posé avant l’entrée en section


critique et déposé ensuite

• la pose assure qu’une seule demande ne peut


être effectivement servie, les autres demandes
sont placées en attente

• en Java il existe le mot-clé synchronized qui


permet de verrouiller une section critique
32
API Java

• deux utilisations possible de synchronized

• on peut verrouiller une méthode entière, auquel cas


le mot-clé est employé dans la signature de la
méthode

• on peut verrouiller un bloc de code en particulier


par emploi de
synchronized(o) {

}

• où o est l’objet (de type quelconque) représentant le


verrou
33
API Java

• une méthode synchronisée

synchronized … méthode(…) {

}

• correspond exactement à

… méthode(…) {
synchronized(this) {

}
}
34
API Java

class MyCode implements Runnable {


private Compteur c;
public MyCode(Compteur c) { this.c = c; }
public void run() {
for (int i=0; i<100000000; i++) {
synchronized(c) {
[Link]([Link]()+1);
}
}
}
}

• Attention à ne pas « synchroniser » n’importe quoi


• Note : on remarquera qu’une forte pénalité est
appliquée à l’exécution; les verrous ont un coût non
négligeable…
• Il est en général difficile de cerner la bonne
granularité
35
API Java

Un autre problème de concurrence


• une section critique n’est pas toujours, pour son bon
fonctionnement, isolée du reste de l’application

• en particulier, on peut vouloir garantir l’unicité en


section critique mais aussi assurer une
interdépendance entre sections critiques

• le problème des producteurs/consommateurs

36
API Java

• songeons au dynamisme d’une structure tripartite :

• les producteurs produisent des objets et les


stockent

• le stockage possède une capacité limitée

• les consommateurs retirent des objets du


stockage pour les utiliser à leur propre fin

• on peut remarquer qu’il s’agit d’un modèle de


communication à canal de capacité limitée

37
API Java

• le problème est dû aux vitesses relatives des


opérations de consommation et production :

• si les producteurs sont plus rapides que les


consommateurs, il va falloir assurer que
lorsque le stockage est plein, la chaîne de
production soit mise en attente...

• si les consommateurs sont plus rapides que


les producteurs, il va falloir assurer l’attente
des consommateurs lorsque le stockage est vide

38
API Java

class Produit {
private String nom;
public Produit(String nom) {
[Link] = nom;
}
public String toString() {
return nom;
}
}

39
API Java

class Stock {
private Produit []leStock;
private int niveauCourant;
private static final int capacitéMaximale = 10;
public Stock() {
leStock = new Produit[capacitéMaximale];
niveauCourant = 0;
}
public boolean addProduit(Produit p) {
if (niveauCourant==capacitéMaximale) return false;
leStock[niveauCourant++] = p;
return true;
}
public Produit removeProduit() {
if (niveauCourant==0) return null;
Produit p = leStock[niveauCourant-1];
leStock[--niveauCourant] = null;
return p;
}
}

40
API Java

class Producteur extends Thread {


private Stock stock; private String nomProduit; private Random random;
Producteur(Stock stock,String nomProduit) {
[Link] = stock; random = new Random(); [Link] = nomProduit;
}
public void run() {
while (true) {
Produit p = new Produit(nomProduit);
try {
[Link]([Link](1000)+1000);
} catch(InterruptedException e) {}
if (![Link](p)) {
do {
try {
[Link]("Producteur "+getId()+" plein");
[Link]([Link](100)+100);
} catch(InterruptedException e) {}
} while (![Link](p));
}
[Link]("Producteur "+getId()+" a rajoute "+p);
}
}
}
41
API Java

class Consommateur extends Thread {


private Stock stock; private Random random;
public Consommateur(Stock stock) {
random = new Random(); [Link] = stock;
}
public void run() {
while (true) {
try {
[Link]([Link](10000)+1000);
} catch(InterruptedException e) {}
Produit p = [Link]();
if (p==null) {
do { try {
[Link]("Consommateur "+getId()+ " vide");
[Link]([Link](100)+100);
} catch(InterruptedException e) {}
p = [Link]();
} while (p==null);
}
[Link]("Consommateur "+getId()+" a enlevé "+p);
}
}
}
42
API Java

public class ProdCons {


public static void main(String []args) {
Stock stock = new Stock();
Producteur []p = new Producteur[2];
p[0] = new Producteur(stock,"banane");
p[1] = new Producteur(stock,"carambar");
p[0].start();
p[1].start();
Consommateur []c = new Consommateur[3];
c[0] = new Consommateur(stock);
c[1] = new Consommateur(stock);
c[2] = new Consommateur(stock);
c[0].start();
c[1].start();
c[2].start();
}
}

43
API Java

• deux problèmes dans ce code :

• la non-atomicité des opérations d’ajout et retrait


du stock

• l’attente active

• nous savons corriger le problème d’atomicité de


l’accès au stock :

• synchronized

44
API Java

• le problème subsistant est que le mécanisme mis en


place encourage l’attente active

• une boucle testant sans arrêt si la condition de


continuation est vraie ou non...

• très consommatrice en ressources (CPU)

• pour empêcher cette attente active, nous avons un


problème :

• nous souhaitons nous mettre en attente que la


condition recherchée soit réalisée alors que nous
avons la clé
45
API Java

• pour se faire, nous allons employer les méthodes


wait() et notify() héritées d’Object

• wait() permet de relâcher un verrou que l’on


possède et de se mettre en attente jusqu’a ce que
quelqu’un nous autorise à tenter de le reprendre

• notify() / notifyAll() permet d’autoriser un


(resp. tous les) thread en attente de tenter de
reprendre le verrou qu’il possédait

• Attention : il FAUT déjà détenir le verrou pour


effectuer ces opérations
46
API Java

class Stock {
private Produit []leStock;
private int niveauCourant;
private static final int capacitéMaximale = 10;
public Stock() {
leStock = new Produit[capacitéMaximale];
niveauCourant = 0;
}
public synchronized void addProduit(Produit p) {
while (niveauCourant==capacitéMaximale) {
[Link]("plein!");
try {
wait();
} catch(InterruptedException e) {}
}
leStock[niveauCourant++] = p;
notifyAll();
}
// to be continued…
47
API Java

public synchronized Produit removeProduit() {


while (niveauCourant==0) {
[Link]("vide!");
try {
wait();
} catch(InterruptedException e) {}
}
Produit p = leStock[niveauCourant-1];
leStock[--niveauCourant] = null;
notifyAll();
return p;
}
} // fin classe Stock

48
API Java

class Producteur extends Thread {


private Stock stock;
private String nomProduit;
private Random random;
Producteur(Stock stock,String nomProduit) {
[Link] = stock;
random = new Random();
[Link] = nomProduit;
}
public void run() {
while (true) {
Produit p = new Produit(nomProduit);
try {
[Link]([Link](1000)+1000);
} catch(InterruptedException e) {}
[Link](p);
[Link]("Producteur "+getId()+" a rajoute "+p);
}
}
}

49
API Java

class Consommateur extends Thread {


private Stock stock;
private Random random;
public Consommateur(Stock stock) {
random = new Random();
[Link] = stock;
}
public void run() {
while (true) {
try {
[Link]([Link](10000)+1000);
} catch(InterruptedException e) {}
Produit p = [Link]();
[Link]("Consommateur "+getId()
+" a enlevé "+p);
}
}
}
50
API Java

[Link]
• depuis la version 1.5, il existe trois packages très complets
contenant des mécanismes de gestion de la concurrence

• [Link] qui contient (entre autres)


des sémaphores, des futurs, des barrières, etc.

• [Link] qui contient


diverses classes de nombres autorisant des opérations
atomiques de modification

• [Link] qui contient divers


types de verrous

51

Vous aimerez peut-être aussi