0% ont trouvé ce document utile (0 vote)
9 vues102 pages

Gestion des processus et threads en Java

java thread et processus

Transféré par

bensalahahmed8603
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)
9 vues102 pages

Gestion des processus et threads en Java

java thread et processus

Transféré par

bensalahahmed8603
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

1

Chapitre

Programmation Concurrente
Programme vs Processus
2

• Un programme et un processus sont des termes liés.

• Programme est un groupe d'instructions permettant d'exécuter une tâche

spécifique

• Processus est une structure permettant le suivi de l’exécution d’un programme.

• Un processus soit une entité active, un programme est considéré comme passif.

• Il existe une relation multiple entre processus et programme, ce qui signifie

qu'un programme peut invoquer plusieurs processus ou, en d'autres termes,

plusieurs processus peuvent faire partie du même programme.


Processus – Ressources Utilisées
3

Ressources de calcul
Processus – Ressources Utilisées
4

Ressources de calcul
Processus – Ressources Utilisées
5

CPU : Central Processing Unit


Processus – Ressources Utilisées
6

CPU : Central Processing Unit


Processus – Ressources Utilisées
7

GPU= Graphic Processing Unit

Le calcul par le GPU consiste à utiliser le processeur graphique (GPU) en


parallèle du CPU pour accélérer vos applications professionnelles de science,
d’analyse, d’ingénierie, de production et d’entreprise. Lancés en 2007 par NVIDIA,
les accélérateurs GPU se sont imposés comme un standard de l’industrie. Dans le
monde entier, la plupart des centres de données à basse consommation y ont
recours, aussi bien dans les laboratoires gouvernementaux et universitaires que
dans les petites et moyennes entreprises.
Processus – Ressources Utilisées
8
9
Processus – Ressources Utilisées
11
Pendant 30 ans, les principes de la Loi de Moore ont fait autorité que les performances des
microprocesseurs augmentaient de 50% par an. Les limitations physiques des semi-
conducteurs font que les performances des CPU n’augmentent plus que de 10% chaque
année. En popularisant le traitement GPU, NVIDIA a offert une nouvelle voie à toute
l’industrie et va fournir d’ici 2025 des performances de calcul jusqu’à 1000 fois plus rapides.
Processus – Ressources Utilisées
12

TPU

Tensor Processing Unit (TPU) est un circuit intégré spécifique comme


accélérateur d'intelligence artificielle développé par Google
spécifiquement pour l'apprentissage automatique des réseaux
neuronaux.
Processus – Ressources Utilisées
13

Java et TPU :
[Link]
Processus – Ressources Utilisées
14

RAM

DDR : Double Data Rate


Processus – Ressources Utilisées
15

Bande Passante
La bande passante désigne le volume de
données qui peut être transféré d’un point à
un autre d’un réseau en un laps de temps
défini. Généralement, elle s’exprime sous la
forme d’un débit binaire, ou bitrate, mesuré en
bits par seconde (bps).

Complexité en messages

Un système distribué, les sites s’échangent en permanence des informations et


l’utilisation d’une stratégie d’acheminement de messages inadaptée surchargera
alors considérablement la bande passante du réseau et conduira inévitablement a
une baisse importante des performances du système.
Cycle d’exécution d’un programme
16
Processus et cache CPU
17

• Un cache rapide est utilisé pour


accéder aux données couramment
utilisées.
• L'accès au cache du processeur est
plus rapide que la mémoire principale
(RAM). Le cache de données est
généralement organisé comme une
hiérarchie de plusieurs niveaux de
cache L1, L2, etc.
• Le cache L1 est le cache le plus rapide
et il se trouve généralement dans la
puce du processeur. Chaque cœur peut avoir son propre
• Le cache L2 se situe entre L1 et la RAM cache L1, donc un thread s'exécutant
dans chaque cœur a sa propre
et est plus grand que L1.
mémoire locale
• Il peut y avoir un cache de troisième
niveau L3, selon le type de CPU.
Contexte du Processus
18

Le context d’un processus inclut:


• Segment des données
• Identificateur
• Stack (pile) : statique
• Etat
• Heap (tas) : dynamique
• Priorité
• Interruptions E/S
• Program counter
• L’ensemble des fichiers utilisés
Contexte du Processus
19

• Système muli-tâches → exécution concurrente de tâches


• Tâche caractérisée à un instant donné par un état
▪ En exécution : tâche disposant du CPU
▪ Prête : tâche susceptible de s’exécuter si elle avait le CPU
▪ Bloquée : tâche attendant l’obtention d’une ressource (I/O, …)
• A chaque tâche est associé un contexte d’exécution
▪ Espace mémoire, registres du CPU, …
• Coopératif ou préemptif (avec politique d’ordonnancement)
Allocation mémoire en Java
20

L'allocation de mémoire en Java est le processus dans lequel les sections de


mémoire virtuelle sont mises de côté dans un programme pour stocker les variables
et les instances de classes.
La mémoire n'est pas allouée à un objet lors de la déclaration mais seule une
référence est créée. Pour l'allocation de mémoire de l'objet, la méthode new() est
utilisée, de sorte que l'objet se voit toujours allouer de la mémoire sur le tas.
Allocation mémoire en Java
21

L'allocation de mémoire Java est divisée en sections suivantes :


▪ Heap
▪ Stack
▪ Code
▪ Static

Cette division de la mémoire est nécessaire à sa gestion efficace.


• La section Code contient le bytecode
• La section Stack de la mémoire contient des méthodes, des variables
locales et des variables de référence
• La section Heap contient des objets
• La section Static contient des données/méthodes statiques
Allocation mémoire en Java - Heap
22

• Young Generation (Nursery) : Tous les nouveaux objets sont alloués dans cette
mémoire. Chaque fois que cette mémoire est remplie, le ramasse-miettes est
effectué. C'est ce qu'on appelle la collecte des ordures mineures.
• Old Generation : tous les objets à longue durée de vie qui ont survécu à de
nombreuses séries de collectes d'ordures mineures sont stockés dans cette
zone. Chaque fois que cette mémoire est remplie, le ramasse-miettes est
effectué. C'est ce qu'on appelle le Major Garbage Collection.

Heap Memory
Allocation mémoire en Java - Heap
23

• Mémoire PermGen : il s'agit d'un espace spécial où tout le contenu statique est
stocké dans cette section.

• PermGen a été complètement supprimé de Java 8. À la place de PermGen, une nouvelle


fonctionnalité appelée Meta Space a été introduite. MetaSpace grandit
automatiquement par défaut. La récupération se déclenche lorsque l'utilisation des
métadonnées de classe atteint sa taille de méta-espace maximale.
Allocation mémoire en Java
24

Combler le fossé entre le modèle de mémoire Java et l'architecture de mémoire matérielle :


• Le modèle de mémoire Java et l'architecture de la mémoire matérielle sont différents.
• L'architecture de la mémoire matérielle ne fait pas de distinction entre les piles de threads et
le tas. Sur le matériel, la pile de threads et le tas sont situés dans la mémoire principale. Des
parties des piles de threads et du tas peuvent parfois être présentes dans les caches du
processeur et dans les registres internes du processeur.
Processus vs Thread
25
Processus vs Thread
26
Processus vs Thread
27
Programmation séquentielle
28

Exécuter une application c’est exécuter la méthode main d’une des classes de
l’application.

A la fin de l’exécution de main l’application est terminée


Programmation récursive
29

 La programmation récursive consiste dans la grande majorité des cas, à


créer une fonction qui s’appelle elle-même une ou plusieurs fois selon
différents critères.
Programmation Concurrente
30
Programmation Parallèle
31
Programmation Parallèle
32

Tri fusion parallèle


Programmation Parallèle
33

Tri rapide parallèle


Programmation Distribuée
34

La programmation distribuée consiste à exécuter le même traitement sur des


données sauvegardées et divisées en parties dans plusieurs machines. Les sous
résultats obtenus sur les différentes machines sont combinés pour avoir le résultat
final. Un modèle de programmation distribué est utilisé, comme MapReduce,
décrivant les étapes à suivre pour concevoir un programme distribué.
Programmation Distribuée
35

Programme MapReduce pour le calcul des fréquences des mots dans un texte
Qu'est-ce que la simultanéité ou le cœur unique ?
36

 Dans un système à temps partagé, la simultanéité est définie comme la


capacité d'un système à exécuter deux programmes ou plus dans des
phases de temps qui se chevauchent.

 L'action du processeur de basculer entre les tâches est connue sous le


nom de commutation de contexte.
Exécution parallèle ou (Multi-Core)?
37

 Dans une exécution parallèle, les tâches à effectuer par un processus sont
décomposées en sous-parties, et plusieurs processeurs (ou plusieurs
cœurs) traitent chaque sous-tâche au même moment.
Exécution parallèle ou (Multi-Core)?
38
Hyper-Threading
39

Transformez une
unité de traitement
physique en deux
unités de traitement
logiques.
Synchronisation des processus
40
Synchronisation des processus
41

• Une section critique est une partie de code telle que 2 processus ne
peuvent s’y trouver au même instant.
• Une ressource est en exclusion mutuelle si seul un processus peut
utiliser la ressource à un instant donné.
• Un sémaphore est une variable et constitue la méthode utilisée
couramment pour restreindre l’accès à des ressources partagées dans un
environnement de programmation concurrente.
• Un moniteur est un mécanisme supplémentaire évitant l’attente active
if ( condition )
wait(); // Appel bloquant
/* Section Critique */
notify(); //réveil d’un thread bloqué
Section Critique
42
Section Critique
43
Programmation concurrente en Java
44

● Notion de concurrence "système" (processus)


– [Link], Process, ProcessBuilder
● Bien mieux géré au niveau de la machine virtuelle
– Classes spécifiques: [Link], Runnable...
– Classes de service: [Link]...
– Cohérence des valeurs (volatile et classes atomics), relativement au
Java Memory Model (JMM, JSR 133)
● Protection des accès et organisation des threads, exclusion mutuelle...
● synchronized, moniteurs, wait()/notify(), [Link],
etc.
Processus en java
45

Objet représentant une application qui s'exécute


– [Link]
• Objet de contrôle de l'environnement d'exécution
• Objet courant récupérable par [Link]()
• D'autres méthodes: [total/free/max]Memory(), gc(), exit(), halt(),
availableProcessors()...
• exec() crée un nouveau processus
– [Link] et ProcessBuilder
• Objets de contrôle d'un (ensemble de) processus, ou commandes
• [Link]().exec("cmd") crée un nouveau processus
correspondant à l'exécution de la commande, et retourne un objet de la
classe Process qui le représente
La classe Runtime
46

• Les différentes méthodes exec() créent un processus natif.


▪ Exemple simple:
[Link]().exec("javac [Link]");
▪ Avec un tableau d'arguments
[Link]().exec(new String[]{"javac", "[Link]"});
▪ Exécute la commande cmd dans le répertoire /tmp avec comme variable
d'environnement var la valeur val.
[Link]()
.exec("cmd",
new String[] {"var=val"},
new File("/tmp/"));
La classe Runtime
47

public class Runtime1{


public static void main(String args[])throws Exception{
[Link]().exec("notepad");//will open a new notepad }
}

public class Runtime2{


public static void main(String args[])throws Exception{
[Link]().exec("shutdown -s -t 0");
}
}

public class Runtime2{


public static void main(String args[])throws Exception{
[Link]().exec("c:\\Windows\\System32\\shutdown -s -t 0");
// use -s switch to shutdown system, -r switch to restart system and -t switch to specify time
//delay.
}
}
La classe Runtime
48

public class Runtime3{ // retourne les processeurs disponibles


public static void main(String args[])throws Exception{
[Link]([Link]().availableProcessors()); }
}

public class MemoryTest{


public static void main(String args[])throws Exception{
Runtime r=[Link]();
[Link]("Total Memory: "+[Link]());
[Link]("Free Memory: "+[Link]());
for(int i=0;i<10000;i++){
new MemoryTest(); }
[Link]("After creating 10000 instance, Free Memory: "+[Link]());
[Link]();
[Link]("After gc(), Free Memory: "+[Link]());
} }
La classe Process
49
• La classe Process abstraite est un processus. Les méthodes fournies par
le processus sont utilisées pour effectuer des entrées, des sorties, attendre que le
processus soit terminé, vérifier l’état de sortie du processus et détruire le
processus.
• Objet retourné par méthode exec() de Runtime
– Process fils= [Link]().exec("commande");
[Link](); // attend la terminaison du processus fils
[Link]([Link]());
– Toutes les méthodes sont abstraites dans Process:
● destroy(), getInputStream(), getOutputStream(), getErrorStream()
– Nécessité de lire et d'écrire dans les flots d'E/S
La classe Process
50

Les méthodes exec de la classe Runtime retournent une instance de la classe Process qui
représente le processus généré par l'exécution de la commande.

void destroy() Destruction du processus.


Retourne l'état de la fin de processus (0 si
int exitValue()
ça s'est bien terminé).
Retourne le fichier de sortie normale du
InputStream getInputStream() processus, dans lequel nous pouvons lire les
écritures faites par le processus.
Retourne le fichier de sortie erreur du
InputStream getErrorStream() processus, dans lequel nous pouvons lire les
écritures faites par le processus.
Retourne le fichier d'entrée du processus,
OutputStream getOutputStream() dans lequel nous pouvons écrire au
processus.
L'application attend la fin de l'exécution du
void waitFor()
processus.
La classe Process
51

public class ProcessDemo


{ public static void main(String[] args) {
try {

// create a new process


[Link]("Creating Process");

Process pro = [Link]().exec("[Link]");


// wait 10 seconds
[Link]("Waiting");
[Link](10000);

// kill the process


[Link]();
[Link]("Process destroyed");
[Link]("Exited with code " + [Link]());
}
catch (Exception ex) {
[Link]();
}
}}
La classe Process
52

import [Link];
public class ProcessDemo {

public static void main(String[] args) throws Exception {

Runtime r = [Link]();
Process p = [Link]("[Link]");
[Link](10, [Link]);
[Link]();
}
}
La classe ProcessBuilder
53

Cette classe est utilisée pour créer des processus. Chaque instance de
ProcessBuilder gère une collection d'attributs de processus.

Gère des "attributs de processus" communs et permet de créer plusieurs processus


– Commande (liste de chaînes de caractères)
● Ex: [ "javac", "[Link]" ]
– Environnement (Map<String, String> variables/valeurs)
● Récupère par défaut les variables d'environnement du système
([Link]())
● Répertoire de travail sous la forme d'un objet File
● Propriété de redirection de flot d'erreur
– redirectErrorStream(boolean redirect) (faux par défaut)
La classe ProcessBuilder
54
La classe ProcessBuilder
55
La classe ProcessBuilder
56
La classe ProcessBuilder
57
● Permet de créer plusieurs Process successivement, à partir du même
ProcessBuilder
– La modification des attributs du ProcessBuilder n'affecte que les Process créés
ultérieurement (pas ceux déjà créés)
ProcessBuilder pb = new ProcessBuilder("script", "arg1", "arg2");
Map<String, String> env = [Link]();
[Link]("newVariable", "newValue");
List<File> dirList = ...;
List<Process> procList = new ArrayList();
for(File workDir : dirList) {
[Link](workDir);
[Link]([Link]());
}// démarre un nouveau Process pour chaque workDir
La classe ProcessBuilder
58
// Java code illustrating ProcessBuilder
// command(List<String> command)
import [Link].*;
import [Link].*;
import [Link].*;
class ProcessBuilderDemo {
public static void main(String[] arg) throws IOException
{
// creating list of process
List<String> list = new ArrayList<String>();
[Link]("[Link]");
[Link]("[Link]");

// create the process


ProcessBuilder build = new ProcessBuilder(list);

// checking the command in list


[Link]("command: " + [Link]());
[Link]();
} command: [[Link], [Link]]
}
La classe ProcessBuilder
59
import [Link];
import [Link];
import [Link];
import [Link];
import [Link];
class ProcessBuilderDemo {
public static void main(String[] arg) throws IOException
{
// creating list of process
List<String> list = new ArrayList<String>();
[Link]("[Link]"); [Link]("/C"); [Link]("start");
[Link]("fileInexistant");
// create the process
ProcessBuilder build = new ProcessBuilder(list);

// checking the command in list


[Link]("command: " + [Link]());
File log = new File("[Link]");
[Link](true);
[Link]([Link](log));
try {
Process p = [Link]();
} catch (IOException e) {
[Link]();
}
}}
La classe ProcessBuilder
60
import [Link];
import [Link];
public class MainClass {
public static void main(String[] args) {
try {
File commands = new File("[Link]");
File output = new File("[Link]");
File errors = new File("[Link]");
ProcessBuilder pb = new ProcessBuilder("PowerShell");
[Link]([Link]().toString());
[Link]([Link]().toString());
[Link]([Link]().toString());

[Link](commands);
[Link](errors);
[Link](output);
[Link]([Link]().toString());
[Link]([Link]().toString());
[Link]([Link]().toString());

[Link]();
} catch (IOException ex) {
[Link]();
}

}}
Java ProcessBuilder inheritIO()
61
• Java ProcessBuilder inheritIO() Définit la source et la destination des E/S
standard du sous-processus pour qu'elles soient identiques à celles du processus
Java père.

• Une invocation de la forme [Link]() se comporte exactement de la même


manière que l'invocation [Link]([Link])
[Link]([Link]) [Link]([Link]).
Java ProcessBuilder inheritIO()
62

import [Link];
public class Main {
public static void main(String[] args) {
// create a new list of arguments for our process
String[] list = { "[Link]", "[Link]" };
// create the process builder
ProcessBuilder pb = new ProcessBuilder(list);
try {
// start the subprocess
[Link]("Starting the process..");
pb = [Link]();
[Link]();
} catch (IOException ex) {
[Link]();
}
}
}
[Link]
69

• Java 9 ajoute une nouvelle façon d'obtenir des informations sur les processus
en cours et sur tous les processus engendrés.
• Permet d’obtenir de nombreuses informations sur le processus via
[Link] :
▪ la commande utilisée pour démarrer le processus
▪ les arguments de la commande
▪ instant où le processus a été lancé
▪ le temps total passé par celui-ci et
▪ l'utilisateur qui l'a créé
• On peut invoquer la méthode toHandle() appliquée sur [Link] pour
obtenir une instance de [Link].
[Link]
70

private static void infoOfCurrentProcess() {


ProcessHandle processHandle = [Link]();
[Link] processInfo = [Link]();
[Link]("PID: " + [Link]());
[Link]("Arguments: " + [Link]());
[Link]("Command: " + [Link]());
[Link]("Instant: " + [Link]());
[Link]("Total CPU duration: " +
[Link]());
[Link]("User: " + [Link]());
}
Les processus légers (threads)
71

Étant donnée une exécution de Java (une JVM)


● un seul processus (au sens système d'exploitation)
● disposer de multiples fils d'exécution (threads) internes
● possibilités de contrôle plus fin (priorité, interruption...)
● c'est la JVM qui assure l'ordonnancement (concurrence)
● espace mémoire commun entre les différents threads
– Deux instructions d'un même processus léger doivent en général
respecter leur séquencement (sémantique)
– Deux instructions de deux processus légers distincts n'ont pas a priori
d'ordre d'exécution à respecter (entre eux)
Deux façons pour spécifier run()
72
● Redéfinir la méthode run() de la classe Thread
class MyThread extends Thread {
public void run() { /* code à exécuter*/ }
}
– Création et démarrage du thread comme ceci:
MyThread t = new MyThread(); puis [Link]();
● Implanter l'interface Runnable
class MyRunnable implements Runnable {
public void run() { /* code à exécuter */ }
}
– Création et démarrage du thread via un objet cible:
MyRunnable cible = new MyRunnable(); // objet cible
Thread t = new Thread(cible); puis [Link]();
Comparaison des deux approches
73
● Pas d'héritage multiple de classes en Java: hériter d'une autre classe?
● Pouvoir faire exécuter un même Runnable à plusieurs threads
Par héritage de Thread
74
public class MyThread extends Thread {
public void run() {
for (int i=0; i<5; i++) {
[Link]("MyThread, en " + i);
try {[Link](500);}
catch (InterruptedException ie) {[Link]();}
}
[Link]("MyThread se termine"); }}
public class Prog1 {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread(); [Link]();
for (int i=0; i<5; i++) {
[Link]("Initial, en " + i);
[Link](300);
}
[Link]("Initial se termine");
}}
Par héritage de Thread
75
public class MyThread extends Thread {
public void run() {
for (int i=0; i<5; i++) {
[Link]("MyThread, en " + i);
try {[Link](500);}
catch (InterruptedException ie) {[Link]();}
}
[Link]("MyThread se termine"); }}
public class Prog1 {
public static void main(String[] args) throws InterruptedException {
Thread t = new MyThread(); [Link]();
for (int i=0; i<5; i++) {
[Link]("Initial, en " + i);
[Link](300);
}
[Link]("Initial se termine");
}}
Par implantation de Runnable
76
public class MyRunnable implements Runnable {
public void run () {
for (int i=0; i<5; i++) {
[Link]("MyRunnable, en " + i);
try { [Link](500); }
catch (InterruptedException ie){[Link]();}
}
[Link]("MyRunnable se termine");
}}
public class Prog2 {
public static void main(String[] args) throws InterruptedException {
MyRunnable cible = new MyRunnable();
Thread t = new Thread(cible); [Link]();
for (int i=0; i<5; i++) {
[Link]("Initial, en " + i);
[Link](300);
}
[Link]("Initial se termine");
}}
start vs run
77

Différence entre la méthode start et run dans les threads en Java


• Lorsque vous appelez directement la méthode run(), le code de la méthode
run() est exécuté dans le même thread qui appelle la méthode run. JVM ne
créera pas de nouveau thread jusqu’à ce que vous appeliez la méthode start().
• En revanche, lorsque vous appelez la méthode [Link](), le code dans la
méthode run() est exécuté comme nouveau thread.
• Une autre différence à retenir entre les méthodes start et run est que vous
pouvez appeler la méthode run() plusieurs fois. JVM ne renvoie aucune erreur,
mais vous ne pouvez pas appeler la méthode start() sur la même instance de
thread.
Thread et objet de contrôle
78

● À la fin de l'exécution de la méthode run() de la cible, le processus léger est


terminé (mort):
– il n'est plus présent dans la JVM (en tant que thread)
– mais printStackTrace(de classe Thread) existe encore
– sa méthode isAlive() retourne false
– il n'est pas possible d'en reprendre l'exécution
– l'objet contrôleur sera récupéré par le ramasse-miettes
● L'objet représentant le thread qui est actuellement en train d’être exécuté peut
être obtenu par la méthode statique [Link]()
La classe [Link]
79
● Chaque instance de la classe Thread possède:
– un nom, [get/set]Name(), un identifiant
– une priorité, [get/set]Priority(),
– les threads de priorité haute sont exécutées + souvent
– trois constantes prédéfinies: [MIN / NORM /MAX]_PRIORITY
– un statut daemon (booléen), [is/set]Daemon()
– un groupe, de classe ThreadGroup, getThreadGroup()
– par défaut, même groupe que la thread qui l'a créée
– une cible, représentant le code que doit exécuter ce processus léger. Ce code
est décrit par la méthode
public void run() {...}
La classe [Link]
80
Daemon Thread
81

o Un thread démon n'empêche pas la JVM de s'arrêter même s'il est encore en
cours d'exécution.

o Les traitements d'un thread démon s'exécutent indéfiniment et ils ne sont pas
interrompus : c'est l'arrêt de la JVM qui provoque leur fin. Lorsque la JVM s'arrête,
elle termine tous les threads démons en cours d'exécution du moment qu'ils soient
les seuls encore actifs.

o Par exemple, les threads du ramasse-miettes sont généralement des démons.

o Par défaut, un nouveau thread hérite de la propriété daemon du thread qui le


lance.
Daemon Thread
82

o Pour préciser qu'un thread est un démon, il faut invoquer sa méthode


setDaemon() en lui passant la valeur true comme paramètre. Cette méthode doit
être invoquée avant que le thread ne soit démarré : une fois le thread démarré,
son invocation lève une exception de type IllegalThreadStateException.

o La méthode isDaemon() renvoie un booléen qui précise si le thread est un démon.

o Lorsque la JVM s'arrête, les threads démons sont arrêtés brutalement : leurs blocs
finally ne sont pas exécutés. C'est la raison pour laquelle, les threads démons ne
devraient pas être utilisés pour réaliser des opérations de type I/O ou des
traitements critiques.
Daemon Thread
83
public class TestThreaddemon {
public static void main(String[] args)
{
Thread daemonThread = new Thread(new Runnable() {
public void run() {
try {
while (true)
{ [Link]("Execution demon"); }
}
finally { [Link]("Fin demon"); } } },
"Demon");
[Link](true);
[Link]();
}
}

Le nombre de messages affichés varie de un à quelques uns avant l'arrêt de la JVM. Le


message du bloc finally n'est jamais affiché.
Threads et JVM
84
● La Machine Virtuelle Java continue à exécuter des threads jusqu'à ce que:
● soit la méthode exit() de la classe Runtime soit appelée
● soit tous les threads non marquées "daemon" soient terminées.
– on peut savoir si un thread est terminé via la méthode isAlive()
● Avant d'être exécutées, les threads doivent être créés:
Thread t = new Thread(...);
● Au démarrage du thread, par [Link]();
● la JVM réserve et affecte l'espace mémoire nécessaire avant d'appeler la
méthode run() de la cible.

Il existe deux catégories de threads :

▪ thread utilisateur (user thread)


▪ démon (daemon thread)
Les groupes de threads
85

o Un groupe de threads permet de regrouper des threads selon différents critères et


de les manipuler en même temps ce qui évite d'avoir à effectuer la même
opération individuellement sur tous les threads. Il permet aussi de définir des
caractéristiques communes aux nouveaux threads qui lui sont ajoutés.
o La notion de groupe permet aussi de limiter l'accès aux autres threads. Chaque
thread ne peut manipuler que les threads de son groupe d'appartenance ou des
groupes subordonnés.
o La classe [Link] encapsule un groupe de threads : elle contient
un ensemble de threads pour permettre de réaliser des opérations de gestion ou
de contrôle sur tous ceux-ci. Elle peut aussi contenir d'autres ThreadGroups qui
forment alors des sous-groupes. Cela permet de créer une hiérarchie dans les
groupes.
Les groupes de threads
86

o Chaque groupe, à l'exception du groupe par défaut, possède un groupe parent.


Chaque thread appartient à un groupe de threads (thread group) :
o soit explicitement dans un groupe de threads précisé en paramètre de l'une
des surcharges du constructeur de la classe thread :
Thread(ThreadGroup group, Runnable runnable),
Thread(ThreadGroup group, String name), Thread(ThreadGroup
group, Runnable runnable, String name)
o soit dans un groupe de threads par défaut si aucun n'est précisé. Par
défaut, lors de la création d'un thread, si aucun groupe n'est précisé alors
c'est le groupe du thread courant qui est utilisé.
o Il existe un groupe de thread par défaut. Au lancement de la JVM, un
ThreadGroup généralement nommé main est créé et sera utilisé comme groupe
de threads par défaut.
Les groupes de threads
87
public class TestThreadGroup {
public static void main(String[] args)
{
Runnable runnable = new MonTraitement();
Thread t = new Thread(runnable);
[Link]("groupe:"+[Link]().getName())
;
[Link]();
}
}

groupe:main
Les groupes de threads
88
o La seule solution pour ajouter un Thread dans un groupe particulier est d'utiliser
une des surcharges du constructeur de la classe Thread qui attend en paramètre un
objet de type ThreadGroup :

o public Thread(ThreadGroup group, Runnable target)


o public Thread(ThreadGroup group, String name)
o public Thread(ThreadGroup group, Runnable target, String name)
o Attention : une fois créé, un thread ne peut pas être déplacé vers un autre groupe.
public class TestThreadGroup {
public static void main(String[] args) {
Runnable runnable = new MonTraitement();
ThreadGroup monThreadGroup = new ThreadGroup("Mon groupe de
threads");
Thread t = new Thread(monThreadGroup, runnable);
[Link]("groupe:" +
[Link]().getName()); [Link]();
}
}
groupe:Mon groupe de threads
Les groupes de threads
90

public class TestThreadGroup {


public static void main(String[] args) throws InterruptedException
{
Runnable runnable = new MonTraitement();
ThreadGroup monThreadGroup = new ThreadGroup("Mon groupe de
threads"); [Link](true);
[Link]("[Link]()=" +
[Link]());
Thread t = new Thread(monThreadGroup, runnable);
[Link]("[Link]()=" + [Link]());
[Link](Thread.NORM_PRIORITY);
[Link]();
[Link]();
[Link]("[Link]()=" +
[Link]());
} [Link]()=true
} [Link]()=false
[Link]()=true
Les groupes de threads
91

public class TestThreadGroup {


public static void main(String[] args) throws InterruptedException {
int nbThreads;
Thread[] threads;
Runnable runnable = new MonTraitement();
ThreadGroup monThreadGroup = new ThreadGroup("Mon groupe de threads");
Thread t = new Thread(monThreadGroup, runnable, "thread groupe 1");
[Link]();
t = new Thread(monThreadGroup, runnable, "thread groupe 2");
[Link]();
ThreadGroup monSousThreadGroup = new ThreadGroup(monThreadGroup, "Mon
sous-groupe de threads");
t = new Thread(monSousThreadGroup, runnable, "thread sous groupe 1");
[Link]();
nbThreads = [Link]();
[Link]("[Link]()=" + nbThreads);
threads = new Thread[nbThreads]; [Link](threads);
for (int i = 0; i < nbThreads; i++) {
[Link]("Thread " + i + " = " + threads[i].getName()); }
}
}
Les groupes de threads
92

Mon traitement thread groupe 1


Mon traitement thread groupe 2
[Link]()=3
Mon traitement thread sous groupe 1
Thread 0 = thread groupe 1
Thread 1 = thread groupe 2
Thread 2 = thread sous groupe 1
Le thread courant
93
public class ThreadExample {
public static void main(String[] args)
throws InterruptedException {
Thread t = [Link]();
// Affiche caractéristiques de la thread courante
[Link](t);
// Lui donne un nouveau nom
[Link]("Médor");
[Link](t);
// Rend le processus léger courant
// inactif pendant 1 seconde
[Link](1000);
[Link]("fin");
}
}
L'arrêt d'un thread
94

Par défaut, l'exécution d'un thread s'arrête pour deux raisons :


• la fin des traitements de la méthode run() est atteinte
• une exception est levée durant les traitements de la méthode run()
Historiquement la classe Thread possède une méthode stop() qui est déclarée
deprecated depuis Java 1.1 et est conservée pour des raisons de compatibilité
mais elle ne doit pas être utilisée car son comportement peut être aléatoire et
inattendu.
Modification de la priorité d'un thread
95
Un thread possède une propriété qui précise sa priorité d'exécution. Pour
déterminer ou modifier la priorité d'un thread, la classe Thread contient les
méthodes suivantes :

Méthode Rôle
int getPriority() retourner la priorité d'exécution du thread
void setPriority(int) modifier la priorité d'exécution du thread

Généralement, la priorité varie de 1 à 10 mais cela dépend de l'implémentation de la


JVM. Plusieurs constantes permettent de connaître les valeurs de la plage de priorités
utilisables et la valeur de la priorité par défaut :
▪ Thread.MIN_PRIORITY : la valeur de la priorité minimale
▪ Thread.MAX_PRIORITY : la valeur de la priorité maximale
▪ Thread.NORM_PRIORITY : la valeur de la priorité normale
La valeur par défaut de la priorité lors de la création d'un nouveau thread est celle du
thread courant.
Modification de la priorité d'un thread
96

public class TestThreadPriority {


public static void main(String[] args) {
[Link]("MIN_PRIORITY : " + Thread.MIN_PRIORITY);
[Link]("MAX_PRIORITY : " + Thread.MAX_PRIORITY);
[Link]("NORM_PRIORITY : " + Thread.NORM_PRIORITY);
}
}

MIN_PRIORITY : 1
MAX_PRIORITY : 10
NORM_PRIORITY : 5

Thread thread = new Thread();


[Link](Thread.MAX_PRIORITY);
Cycle de vie d'un processus léger
97

● Création de l'objet contrôleur: t = new Thread(...)


● Allocation des ressources: [Link]()
● Début d'exécution de run()
– [éventuelles] suspensions temp. d'exéc: [Link]()
– [éventuels] relâchements voulus du proc. : [Link]()
– peut disposer du processeur et s'exécuter
– peut attendre le proc. ou une ressource pour s'exécuter
● Fin d'exécution de run()
● Ramasse-miettes sur l'objet de contrôle
Cycle de vie d'un processus léger
98
Cycle de vie d'un processus léger
99

Valeur Description
Le thread n'est pas encore démarré. Aucune ressource système ne lui est encore affectée. Seules
NEW
les méthodes de changement de statut du thread start() et stop() peuvent être invoquées
RUNNABLE Le thread est en cours d'exécution : sa méthode start() a été invoquée
BLOCKED Le thread est en attente de l'obtention d'un moniteur qui est déjà détenu par un autre thread
Le thread est en attente d'une action d'un autre thread ou que la durée précisée en paramètre de
la méthode sleep() soit atteinte.
Chaque situation d'attente ne possède qu'une seule condition pour retourner au statut
Runnable :
•si la méthode sleep() a été invoquée alors le thread ne retournera à l'état Runnable que lorsque
le délai précisé en paramètre de la méthode a été atteint
WAITING
•si la méthode suspend() a été invoquée alors le thread ne retournera à l'état Runnable que
lorsque la méthode resume sera invoquée
•si la méthode wait() d'un objet a été invoquée alors le thread ne retournera à l'état Runnable
que lorsque la méthode notify() ou notifyAll() de l'objet sera invoquée
•si le thread est en attente à cause d'un accès I/O alors le thread ne retournera à l'état Runnable
que lorsque cet accès sera terminé
Le thread est en attente pendent un certain temps d'une action d'un autre thread. Le thread
TIMED_WAITING
retournera à l'état Runnable lorsque cette action survient ou lorsque le délai d'attente est atteint
Le thread a terminé son exécution. La fin d'un thread peut survenir de deux manières :
TERMINATED •la fin des traitements est atteinte
•une exception est levée durant l'exécution de ses traitements
Cycle de vie d’un Thread - sleep
100
• La méthode static sleep() de la classe Thread permet de mettre en sommeil le
thread courant pour le délai en millisecondes dont la valeur est fournie en
paramètre.
• Elle est bloquante (wait, sleep,…), elle lève une exception de type
InterruptedException dans le cas où le thread est interrompu.
• La méthode sleep() est static : elle ne s'applique que sur le thread courant et il
n'est pas possible de désigner le thread concerné.
• Contrairement à la méthode wait() de la classe Object, la méthode sleep() ne
libère pas les verrous qui sont posés par le thread.

try {
[Link](5000);
}
catch (InterruptedException e)
{ [Link](); }
Cycle de vie d’un Thread - join
101
• La méthode join() de la classe Thread permet d'attendre la fin de l'exécution du
thread. Elle peut lever une exception de type InterruptedException.
• Une surcharge de la méthode join() attend en paramètre un entier long qui
définit la valeur en millisecondes d'un délai d'attente maximum.
[Link](1000);
Cycle de vie d’un Thread - join
102
class MonRunnable implements Runnable {
private int iter; private String mes;
public MonRunnable(int iter, String mes) { [Link] = iter;
[Link]=mes; }
public void run() {
for(int i=0;i<iter;i++) {
[Link](i+"-"+mes);
}}}
public class ExemplesJoin
{
public static void main(String[] args) throws
InterruptedException {
Thread t1=new Thread(new MonRunnable(5,"th1"));
Thread t2=new Thread(new MonRunnable(5,"th2"));
[Link]();
[Link]();
[Link]();
[Link]("Fin");
}
}
Cycle de vie d’un Thread - yield
103

▪ La méthode static yield() de la classe Thread tente de mettre en pause le thread


courant pour laisser une chance aux autres threads de s'exécuter.
Attention : il n'y a aucune garantie sur le résultat de l'invocation de la méthode
yield() car elle est dépendante de l'implémentation de la JVM.
▪ Théoriquement, « céder » signifie lâcher prise, abandonner, se rendre. Un thread
productif indique à la machine virtuelle qu'elle est disposée à laisser d'autres
threads être exécutés à sa place. Cela indique qu'il ne fait pas quelque chose de
trop critique.
Cycle de vie d’un Thread - yield
104
public class JavaYieldExp extends Thread
{
public void run()
{
for (int i=0; i<3 ; i++)
[Link]([Link]().getName() + " in control");
}
public static void main(String[]args)
{
JavaYieldExp t1 = new JavaYieldExp();
JavaYieldExp t2 = new JavaYieldExp();
// this will call run() method
[Link]();
[Link]();
for (int i=0; i<3; i++)
{
// Control passes to child thread
[Link]();
[Link]([Link]().getName() + " in control");
}
}
}
Cycle de vie d’un Thread - yield
105
public class YieldExample
{ public static void main(String[] args)
{
Thread producer = new Producer();
Thread consumer = new Consumer();

[Link](Thread.MIN_PRIORITY); //Min Priority


[Link](Thread.MAX_PRIORITY); //Max Priority

[Link]();
[Link]();
}
}
class Producer extends Thread
{
public void run() {
for (int i = 0; i < 5; i++)
{
[Link]("I am Producer : Produced Item " + i);
[Link]();
}
}
}
Cycle de vie d’un Thread - yield
106
class Consumer extends Thread
{
public void run()
{
for (int i = 0; i < 5; i++)
{
[Link]("I am Consumer : Consumed Item " + i);
[Link]();
}
}
}
Output du programme “sans” yield() Output du programme “avec” yield()
I am Consumer : Consumed Item 0 I am Producer : Produced Item 0
I am Consumer : Consumed Item 1 I am Consumer : Consumed Item 0
I am Consumer : Consumed Item 2 I am Producer : Produced Item 1
I am Consumer : Consumed Item 3 I am Consumer : Consumed Item 1
I am Consumer : Consumed Item 4 I am Producer : Produced Item 2
I am Producer : Produced Item 0 I am Consumer : Consumed Item 2
I am Producer : Produced Item 1 I am Producer : Produced Item 3
I am Producer : Produced Item 2 I am Consumer : Consumed Item 3
I am Producer : Produced Item 3 I am Producer : Produced Item 4
I am Producer : Produced Item 4 I am Consumer : Consumed Item 4
L'accès au processeur
107

● Différents états possibles d'un thread


– exécute son code cible (il a accès au processeur)
– attend l'accès au processeur (mais pourrait exécuter)
– attend un événement particulier (pour pouvoir exécuter)
● L'exécution de la cible peut libérer le processeur
– si elle exécute un yield() (demande explicite)
– si elle exécute une méthode bloquante (sleep(), wait()...)
● Sinon, c'est l'ordonnanceur de la JVM qui répartit l'accès des threads au
processeur.
– utilisation des éventuelles priorités
Différents états d'un processus léger
108

Depuis 1.5, il est possible de connaître l'état d’un processus léger via la méthode
getState(), exprimé par un type énuméré de type [Link] :
▪ NEW : pas encore démarré;
▪ RUNNABLE : s'exécute ou attend une ressource système, par exemple le
processeur;
▪ BLOCKED : est bloqué en attente d'un moniteur;
▪ WAITING : thread en attente;
▪ TIMED_WAITING : attente bornée par une durée;
▪ TERMINATED : a fini d'exécuter son code.
Arrêt d'un processus léger
109

● Les méthodes stop(), suspend(), resume() sont dépréciées


– Risquent de laisser le programme dans un "sale" état !
● La méthode destroy() n'est pas implantée
– Spécification trop brutale: l'oublier
● Un thread se termine normalement lorsqu’il a terminé d'exécuter
sa méthode run()
– obliger proprement à terminer cette méthode
Interrompre un thread
110
● La méthode interrupt() appelée sur un thread t
– Positionne un « statut d'interruption »
– Si t est en attente parce qu’il exécute un wait(), un join() ou un
sleep(), alors ce statut est réinitialisé et le thread reçoit une
InterruptedException
– Si t est en attente I/O sur un canal interruptible
([Link]), alors ce canal est fermé, le
statut reste positionné et le thread reçoit une
ClosedByInterruptException

Common questions

Alimenté par l’IA

La classe java.lang.Process permet de gérer l'exécution des processus système, en fournissant des méthodes pour réaliser des entrées/sorties, attendre la fin d'un processus, vérifier son état de sortie et le détruire. Un objet Process est généralement retourné par la méthode exec() de Runtime, et peut être utilisé pour interagir avec le processus en cours . La classe ProcessBuilder, quant à elle, est utilisée pour créer et configurer des processus. Elle gère une collection d'attributs de processus et permet de démarrer plusieurs processus avec une configuration commune .

L'utilisation de processus dans une application Java peut mener à une gestion mémoire moins efficace par rapport aux threads, car chaque processus possède son espace mémoire distinct, entraînant une surcharge en termes de swapping et de context switching. Les threads, en revanche, partagent le même espace mémoire, ce qui facilite la communication et réduit la surcharge. Cependant, les processus sont généralement plus robustes car ils sont isolés entre eux, offrant une sécurité accrue en cas de défaillance logicielle d'un processus donné . D'où une meilleure performance des threads en termes de rapidité et consommation de mémoire, mais une possible complexité accrue pour garantir l'exactitude des données manipulées concurremment .

L'appel de la méthode start() sur un objet Thread en Java entraîne la création d'un nouveau thread d'exécution distinct, qui exécute le code défini dans la méthode run(). En revanche, appeler directement la méthode run() n'entraîne pas la création d'un thread séparé ; le code de run() est exécuté dans le thread courant. Cela signifie que pour exploiter le parallélisme, start() doit être utilisé plutôt que run(), car seul start() peut initier un nouveau fil d'exécution .

Le modèle d'exclusion mutuelle est crucial pour éviter les conditions de compétition en programmation concurrente, garantissant qu'une ressource partagée ne soit utilisée que par un seul processus à la fois. Il est implémenté à l'aide de primitives telles que les sémaphores qui contrôlent l'accès à ces ressources . Des structures comme les moniteurs encapsulent les mécanismes de synchronisation, empêchant l'attente active par l'intermédiaire d'opérations de blocage comme wait() et notify().

L'utilisation d'un ThreadGroup en Java offre l'avantage de pouvoir gérer plusieurs threads de manière collective, en facilitant des opérations globales comme le changement de priorité. Il permet également de définir des caractéristiques communes pour les nouveaux threads et de limiter l'accès aux threads entre différents groupes . Cependant, une fois qu'un thread est associé à un ThreadGroup, il ne peut pas être déplacé vers un autre groupe, ce qui peut restreindre la flexibilité de gestion dynamique des threads .

La méthode inheritIO() de ProcessBuilder définit la source et la destination des entrées/sorties standard du sous-processus pour qu'elles soient identiques à celles du processus parent Java. C'est une méthode pour s'assurer que les entrées/sorties standard du sous-processus (i.e., stdin, stdout, et stderr) sont directement reliées à celles du processus appelant, simplifiant l'interaction avec les processus enfants . Cela est équivalent à utiliser pb.redirectInput(Redirect.INHERIT), pb.redirectOutput(Redirect.INHERIT) et pb.redirectError(Redirect.INHERIT).

La synchronisation des processus améliore la gestion des ressources en assurant que deux processus ne peuvent pas accéder simultanément à une section critique, ce qui permet de prévenir les conditions de compétition. Une ressource est en exclusion mutuelle si seul un processus peut l'utiliser à un instant donné. Des mécanismes tels que les sémaphores et les moniteurs sont utilisés pour restreindre l'accès aux ressources partagées, permettant ainsi une gestion efficace des ressources en environnement concurrent .

Les sémaphores sont préférables lorsque la coordination des accès à plusieurs ressources est nécessaire, notamment lorsque les ressources sont limitées et doivent être distribuées de manière contrôlée, comme dans le problème des producteurs-consommateurs. Ils offrent la flexibilité de signaler plusieurs ressources disponibles (via un compteur), contrairement aux moniteurs, qui sont plus adaptés à la synchronisation d'accès unique à une ressource, sans capacité de comptage intrinsèque . Les sémaphores permettent aussi de réduire la contention en fournissant une approche non-bloquante pour vérifier la disponibilité d'une ressource .

Les threads daemons ne bloquent pas l'arrêt de la JVM ; ils s'exécutent en arrière-plan pour effectuer des tâches qui ne doivent pas empêcher la JVM de s'arrêter. Lorsqu'aucun thread utilisateur n'est actif, la JVM s'arrête, terminant ainsi les threads daemons . Par ailleurs, les threads daemons ne doivent pas être utilisés pour des opérations critiques comme les entrées/sorties, car leur interruption ne garantit pas l'exécution des blocs finally .

Le système de gestion des threads en Java garantit la compatibilité avec le système d'exploitation en utilisant la JVM pour gérer l'ordonnancement des threads, permettant ainsi un contrôle plus fin sur la concurrence. Java dispose d'une abstraction qui masque les différences entre systèmes d'exploitation, en fournissant des fonctionnalités telles que la gestion de la priorité des threads et l'allocation de mémoire partagée entre les threads . De plus, l'utilisation de la méthode Thread.start() permet d'assurer que chaque thread s'exécute dans son propre contexte, facilitant l'intégration avec les conventions des OS sous-jacents .

Vous aimerez peut-être aussi