Cours Java Thread
Cours Java Thread
Cours Java Thread
Frédéric Gava
L.A.C.L
Laboratoire d’Algorithmique, Complexité et Logique
Cours de M1 MIAGE
(d’après les notes de cours de Fabrice Mourlin)
Plan
1 Du multi-tâche
2 Cycle de vie
Plan
1 Du multi-tâche
2 Cycle de vie
Du multi-tâche
Cycle de vie
Déroulement du cours
1 Du multi-tâche
2 Cycle de vie
Problématique (1)
Introduction
Comment faire réaliser plusieurs tâches en même temps ? En
confiant ces tâches à des “processus” différents :
Concurrence/parallèle (mémoire partagée) :
sur un même processeur (ordonnacement)
multi-coeurs, multi-processeur, GPU, etc.
Distribué/Répartie (plusieurs machines)
Un mixe des 2 (hybride)
Mobile/agents (calcules qui se déplacent)
En Java
Java dispose d’un mécanisme de “processsus légers” (threads) qui
s’exécutent en parallèle au sein d’une même JVM (concurrence).
Parfois sur plusieurs coeurs/processeurs. Il existe aussi des
bibliothèques standards (ou non) pour le calcul réparti/mobiles/etc.
Programmation avancée et répartie en Java 4 / 27
Du multi-tâche
Cycle de vie
Problématique (2)
Dans ce cours
La notion de Thread
Création de Threads, gestion du code et des données
Cycle de vie d’un Thread et contrôles de l’exécution
Par la suite
Accès concurrents
Appels distants
Échanges de messages (valeurs) : réseau et MPI
Codes natif, références faibles, Programmation dynamique
E/S et accès SBGD
Sécurité et internationalisation
Process exec(...);
L’ordonnanceur (1)
L’ordonnanceur (2)
L’ordonnanceura un rôle essentiel : c’est lui qui décide quel sera le
processus actif à un moment donné et pour combien de temps.
Il existe différents algorithmes d’ordonnancement : p. ex. le
“time-slicing” alloue des tranches de temps pour chaque processus.
Dans certain OS, un processus actif garde “la main” jusqu’à ce
qu’il se bloque sur un appel système (p. ex. une opération d’E/S).
Ces algorithmes sont complexes car ils tiennent compte de facteurs
supplémentaires comme des priorités entre processus.
On trouve différent ordonnanceurs : celui de l’OS (ou de
l’hyperviseur) qui gère les processus (programmes) et donc la JVM.
Celui de la JVM pour les threads. Le processeur, par hyper
threading, peut modifier l’ordre d’exécution des instructions. Quid
des multi-coeurs ? Les ordonnanceurs modernes (JVM, Linux) sont
capables d’utiliser les différents processeurs d’un multi-coeurs.
Impossible de faire des hypothèses sur leurs comportements.
Programmation avancée et répartie en Java 9 / 27
Du multi-tâche
Cycle de vie
Les threads
Dans un processus, il existe des tâches (threads, processus
”légers“) qui partagent les mêmes données. Une fois créée cette
tâche va disposer d’une pile d’exécution qui lui est propre et
partager des codes et des données des classes. La JVM pourra soit
associer un thread Java à un thread système soit utiliser son propre
algorithme d’ordonnancement. Le programmeur ne doit donc pas
faire d’hypothèses sur le comportement de ordonnanceur.
Création (1)
La classe Java Thread permet d’exécuter une tâche. Une instance
de Thread va disposer de caractéristiques propres comme un nom
permettant de l’identifier ou une priorité. Le code exécuté par le
Thread est fourni par une autre classe qui réalise le contrat
d’interface Runnable au travers de sa méthode run() :
import java.io.∗ ;
public class Compteur implements Runnable {
public final int max ;
public Compteur( int max){this.max = max;}
public void run () {
for(int ix = 0 ; ix < max ; ix++) {
try {
File temp = File.createTempFile(”cpt”,””);
System.out.println(”#”+this+”:”+temp);
temp.delete() ;
} catch (IOException exc){/∗test ∗/}
...
Ceci nous permet la définition d’une tâche comme :
Thread tache = new Thread(new Compteur(33)) ;
Programmation avancée et répartie en Java 11 / 27
Du multi-tâche
Cycle de vie
Création (2)
Déroulement du cours
1 Du multi-tâche
2 Cycle de vie
Une fois créé le Thread est prêt à être activé : ceci se fait par
l’invocation de la méthode start() sur l’instance. A partir de ce
moment le code du run() est pris en charge par l’ordonnanceur. En
fonction des décisions de l’ordonnanceur le Thread passe par une
série d’états : actif (le code tourne) ou éligible (le code ne tourne
pas, mais l’ordonnanceur peut le réactiver à tout moment).
Vivant ?
Pour tester si un Thread est “vivant” : if tache.isAlive() { ...}
Se rejoindre
Interruption et priorité
Thread patience = new Thread() {
public void run() {
while(!isInterrupted()) {System.out.print(’.’) ;}
}
};
patience.start();
..... //on fait des tas de choses
patience.interrupt() ;
try {patience.join() ;}
catch(InterrruptedException xc) {...}
Cycle de vie
Image de la mémoire
Le partage de données par plusieurs threads pose des problèmes. Il
faut savoir qu’en Java les variables sont stockées dans une
mémoire principale qui contient une copie de référence de la
variable. Chaque Thread dispose d’une copie de travail des
variables sur lesquelles il travaille. Les mises à jour réciproques de
ces versions d’une même variable reposent sur des mécanismes
complexes qui permettent des réalisations performantes de JVM.
Il est possible de forcer des réconciliations de valeurs en déclarant
des variables membres comme volatile.
public class Partage {
public volatile int valeurCourante;
...
}
Volatile
Rôle
Utilisation sur des variables modifiables de manière asynchrone
(plusieurs threads y ont accès “simultanément”). Le fait
d’employer volatile oblige la JVM à rafraı̂chir son contenu à
chaque utilisation. Ainsi, on est sûr qu’on n’accède pas à la valeur
mise en cache mais bien à la valeur correcte.
Déclaration
public volatile Integer = 5;