Les threads en Java
Transcription
Les threads en Java
Les threads en Java Arnaud Labourel Courriel : [email protected] Université de Provence 26 janvier 2012 Arnaud Labourel, [email protected] Les threads en Java Au Temps des Dinosaures... et des cartes perforées, les ordinateurs étaient de grosses installations sur lesquelles on pouvait soumettre un travail puis venir récupérer le résultat quelques heures plus tard. Si un problème se produisait en cours d’exécution, on ne le savait que plus tard et le temps affecté était perdu... Arnaud Labourel, [email protected] Les threads en Java Multi-Tâches Le multi-tâches permet de optimiser l’utilisation des ressources (CPU, ...) faciliter la programmation (modularisation) faciliter l’équité Serveur multi-utilisateurs Station mono-utilisateur : système plus “réactif” Arnaud Labourel, [email protected] Les threads en Java Processus et Threads Une activité est une suite d’instructions (exécution séquentielle.) Un système est multi-tâche s’il peut faire coexister plusieurs activités au sein d’un même environnement. Un processus est une activité seule au sein d’un environnement restreint (mémoire ...). Au contraire un thread (ou processus léger) partage tout un environnement mémoire avec d’autres threads. Arnaud Labourel, [email protected] Les threads en Java Exemples Activités : processus UNIX threads POSIX threads Java Systèmes d’exploitation : Windows (NT, 2000, XP, Vista, seven) Solaris Unix : Les Unix propriétaires (AIX, HP UX, ...) BSD Linux (vrais threads depuis 2.6) Mac OS X Arnaud Labourel, [email protected] Les threads en Java Création de Threads en Java Deux manières de faire : Classe Thread méthode run : code de l’activité méthode start : lancement de l’activité méthodes de contrôle ... =⇒par héritage de Thread. Arnaud Labourel, [email protected] Les threads en Java Création de Threads en Java Deux manières de faire : Classe Thread méthode run : code de l’activité méthode start : lancement de l’activité méthodes de contrôle ... =⇒par héritage de Thread. interface Runnable une unique méthode (abstraite) public void run() : code de l’activité par implémentation de Runnable + constructeurs de la classe Thread Arnaud Labourel, [email protected] Les threads en Java Rappels : “Héritages Multiples” Pourquoi deux manières de faire ? Rappel : Java n’autorise pas l’héritage multiple. Une classe ne peut être l’extension (ne peux hériter) que d’une seule classe. Par contre elle peut implémenter plusieurs interfaces. Rappel : une interface n’a pas de variable d’instance et n’a que des méthodes abstraites... Arnaud Labourel, [email protected] Les threads en Java Comment choisir entre les deux solutions ? Hériter de Thread : quand on n’a pas besoin d’hériter d’une autre classe. Implémenter Runnable : quand on souhaite hériter d’une autre classe. Arnaud Labourel, [email protected] Les threads en Java Exemple : ExThread.java c l a s s ExThread extends Thread { int id ; ExThread ( i n t v a l ) { /∗ c o n s t r u c t e u r ∗/ id = val ; } /∗ Méthode e x é c u t é e p a r l e t h r e a d ∗/ p u b l i c void r u n ( ) { ... } ... f o r ( i n t i =1; i <=n ; i ++) new ExThread ( i ) . s t a r t ( ) ; ... } Arnaud Labourel, [email protected] Les threads en Java Exemple : ExRunnable.java c l a s s ExRunnable implements R u n n a b l e { int id ; ExThread ( i n t v a l ) { /∗ c o n s t r u c t e u r ∗/ id = val ; } /∗ Méthode e x é c u t é e p a r l e t h r e a d ∗/ p u b l i c void r u n ( ) { ... } ... f o r ( i n t i =1; i <=n ; i ++) new Thread (new ExRunnable ( i ) ) . s t a r t ( ) ; ... } Arnaud Labourel, [email protected] Les threads en Java Remarque Importante Ne pas confondre l’activité (le thread : processus léger) avec la structure de données (le Thread : l’objet). Arnaud Labourel, [email protected] Les threads en Java Information sur les Threads static int activeCount() : renvoie le nombre de threads actuellement exécutés static int enumerate(Thread[] tarray ) : stocke l’ensemble des Threads du même groupe dans le tableau et renvoie le nombre de threads. static Thread currentThread() : renvoie le Thread correspondant au thread en train d’être exécuté. (utile notamment avec Runnable) Arnaud Labourel, [email protected] Les threads en Java Autres méthodes sur les Threads void setName(String name) : change le nom du Thread String getName() : retourne le nom du Thread new Thread(ThreadGroup group, Runnable target , String name) : crée un Thread dans un groupe. new ThreadGroup(”Mon groupe”) : crée un groupe de Thread Arnaud Labourel, [email protected] Les threads en Java Ordonnancement L’ordonnanceur de la JVM n’est pas tenu d’être équitable. Les threads de basses priorités ne sont exécutés que si les threads de hautes priorités sont bloqués... void setPriority ( int ) : fixer la priorité du thread int getPriority () : renvoie la priorité du thread static void yield () : provoque une “pause” du thread en cours et un réordonnancement (indicatif seulement...) L’ordonnanceur n’est pas tenu de tenir compte des priorités... =⇒inutilisable de manière portable. Arnaud Labourel, [email protected] Les threads en Java Manipulation des Threads mise en attente : void sleep (long millis ) void sleep (long millis , int nanos) NB : pas de garantie temps-réel attente de l’arrêt d’un thread donné : void join () void join (long millis ) avec délai void join (long millis , int nanos) avec délai interruption d’un thread : void interrupt () : un thread “interrompt” (envoie une InterruptedException ) un autre. Arnaud Labourel, [email protected] Les threads en Java Rappels : Exceptions Certaines des méthodes précédentes (bloquantes) demandent une gestion explicite des interruptions par try{ ... } catch(InterruptedException e){ ... } Une exception correspond en général à un déroulement anormal du programme. De plus, dans le cas multi-threadé, cela peut correspondre à certaines interactions entre threads. Erreur inhabituelle =⇒gestion d’erreur classique Déclenchement d’une exception Gestion des interruptions/exceptions. Arnaud Labourel, [email protected] Les threads en Java Throwable unchecked exception : erreur engendrée par la JVM (interaction système) =⇒ne doit pas nécessairement être gérée. checked exception : erreur définie par l’utilisateur, doit nécessairement être gérée (=⇒throws) Ici, plus particulièrement, InterruptedException . Arnaud Labourel, [email protected] Les threads en Java try, catch, finally ... try { /∗ a p p e l à une méthode l a n ç a n t d e s e x c e p t i o n s ∗/ ... } catch ( T y p e E x c e p t i o n e ){ ... } catch ( A u t r e T y p e E x c e p t i o n e ){ ... } f i n a l l y { // t o u j o u r s e x é c u t é ... } Arnaud Labourel, [email protected] Les threads en Java Variables Partagées Les variables de la classe déclarées en static sont partagées entre tous les threads associés à cette classe. Il est conseillé de rajouter le mot clef volatile pour protéger la variable de certaines “optimisations” de la JVM. Voir, plus tard, le package atomic, puis les questions de visibilité et du modèle mémoire Java. Arnaud Labourel, [email protected] Les threads en Java Synchronisation et Exclusion Mutuelle Le mot-clef synchronized permet de gérer la synchronisation des accès à la mémoire partagée. Un appel à une méthode déclarée synchronized d’une instance donnée est en exclusion mutuelle vis-à-vis de tous les appels de méthodes déclarées synchronized sur cette même instance. Arnaud Labourel, [email protected] Les threads en Java Très Important : Méthodologie Il faudra toujours bien séparer données partagées et activités =⇒au moins Deux classes. Voir VariablePartagee.java, VariableSynchronized.java, VariableBienSynchronized.java. Arnaud Labourel, [email protected] Les threads en Java VariablePartagee.java class EntierPartage { int valeur ; E n t i e r P a r t a g e ( i n t v a l e u r ){ this . valeur = valeur ; } v o i d d e c r e m e n t e ( i n t d e c ){ v a l e u r −= d e c ; p u b l i c void run ( ) { w h i l e ( v a r p a r t . v a l e u r >= i d ){ a f f i c h e ( ” Avant ”+v a r p a r t ) ; v a r p a r t . decremente ( i d ) ; a f f i c h e ( ” A p rè s ”+v a r p a r t ) ; try {sleep (0 ,0);} c a t c h ( I n t e r r u p t e d E x c e p t i o n e ){ a f f i c h e } } a f f i c h e ( ” Terminé ( ”+v a r p a r t+” ) ” ) ; } } v o i d a f f i c h e ( S t r i n g s ){ System . o u t . p r i n t l n ( Thread . c u r r e n t T h r e a d ( ) . } p u b l i c S t r i n g t o S t r i n g (){ return String . valueOf ( valeur ) ; } p u b l i c s t a t i c v o i d main ( S t r i n g [ ] i n t n = 3 , v a l =9; } c l a s s V a r i a b l e P a r t a g e e e x t e n d s Thread { static v o l a t i l e EntierPartage varpart ; int id ; v a r p a r t = new E n t i e r P a r t a g e ( v a l ) ; f o r ( i n t i =1; i<=n ; i ++) new V a r i a b l e P a r t a g e e ( i ) . s t a r t ( ) ; System . o u t . p r i n t l n ( ” Main : v a l e u r = ”+v a l ) System . o u t . p r i n t l n ( ” Main : I l y a ”+n+” t h System . o u t . p r i n t l n ( ” Main : p e u t r e t i r e r qu ’ VariablePartagee ( int id ) { this . id = id ; } } } Arnaud Labourel, [email protected] args ) { Les threads en Java VariablePartageeSynchronized.java class EntierPartage { int valeur ; E n t i e r P a r t a g e ( i n t v a l e u r ){ this . valeur = valeur ; } s y n c h r o n i z e d v o i d d e c r e m e n t e ( i n t d e c ){ v a l e u r −= d e c ; } p u b l i c S t r i n g t o S t r i n g (){ return String . valueOf ( valeur ) ; } } class VariablePartageeS e x t e n d s Thread { static EntierPartage varpart ; int id ; p u b l i c void run ( ) { w h i l e ( v a r p a r t . v a l e u r >=i d ){ a f f i c h e ( ” Avant ”+v a r p a r t ) ; v a r p a r t . decremente ( i d ) ; a f f i c h e ( ” A p rè s ”+v a r p a r t ) ; try {sleep (0 ,0);} c a t c h ( I n t e r r u p t e d E x c e p t i o n e ){ a f f i c h e (” Interrompu ” ) ; } } a f f i c h e ( ” Terminé ( ”+v a r p a r t+” ) ” ) ; } v o i d a f f i c h e ( S t r i n g s ){ System . o u t . p r i n t l n ( Thread . c u r r e n t T h r e a d ( ) . getName ()+ ” : ”+s ) ; } p u b l i c s t a t i c v o i d main ( S t r i n g [ ] i n t n = 3 , v a l =9; v a r p a r t = new E n t i e r P a r t a g e ( v a l ) ; f o r ( i n t i =1; i<=n ; i ++) new V a r i a b l e P a r t a g e e S ( i ) . s t a r t ( ) ; System . o u t . p r i n t l n ( ” Main : v a l e u r = ”+v a l ) ; System . o u t . p r i n t l n ( ” Main : I l y a ”+n+” t h r e a d s , +” l a r è g l e du j e u e s t c h a q u e t h r e a d ne ” ) ; System . o u t . p r i n t l n ( ” Main : p e u t r e t i r e r qu ’ un ” +” nombre de p o i n t s é g a l e à s o n i d e n t i t é . \ n” ) VariablePartageeBS ( int id ) { this . id = id ; } } Arnaud Labourel, [email protected] args ) { Les threads en Java VariablePartageeBienSynchronized.java class EntierPartage { int valeur ; E n t i e r P a r t a g e ( i n t v a l e u r ){ this . valeur = valeur ; } s y n c h r o n i z e d v o i d d e c r e m e n t e ( i n t d e c ){ v a l e u r −= d e c ; } s y n c h r o n i z e d b o o l e a n t e s t e ( i n t v ){ r e t u r n ( v a l e u r >=v ) ; } p u b l i c S t r i n g t o S t r i n g (){ return String . valueOf ( valeur ) ; } } p u b l i c void run ( ) { w h i l e ( v a r p a r t . t e s t e ( i d ) ){ a f f i c h e ( ” Avant ”+v a r p a r t ) ; v a r p a r t . decremente ( i d ) ; a f f i c h e ( ” A p rè s ”+v a r p a r t ) ; try {sleep (0 ,0);} c a t c h ( I n t e r r u p t e d E x c e p t i o n e ){ a f f i c h e (” Interrompu ” ) ; } } a f f i c h e ( ” Terminé ( ”+v a r p a r t+” ) ” ) ; } v o i d a f f i c h e ( S t r i n g s ){ System . o u t . p r i n t l n ( Thread . c u r r e n t T h r e a d ( ) . getName ()+ ” : ”+s ) ; } p u b l i c s t a t i c v o i d main ( S t r i n g [ ] i n t n = 3 , v a l =9; v a r p a r t = new E n t i e r P a r t a g e ( v a l ) ; f o r ( i n t i =1; i<=n ; i ++) new V a r i a b l e P a r t a g e e B S ( i ) . s t a r t ( ) ; System . o u t . p r i n t l n ( ” Main : v a l e u r = ”+v a l ) ; System . o u t . p r i n t l n ( ” Main : I l y a ”+n+” t h r e a d s , +” l a r è g l e du j e u e s t c h a q u e t h r e a d ne ” ) ; System . o u t . p r i n t l n ( ” Main : p e u t r e t i r e r qu ’ un ” +” nombre de p o i n t s é g a l e à s o n i d e n t i t é . \ n” ) class VariablePartageeBS e x t e n d s Thread { static EntierPartage varpart ; int id ; VariablePartageeBS ( int id ) { this . id = id ; } Arnaud Labourel, [email protected] args ) { } Les threads en Java Problèmes de Coexistence L’existence de plusieurs activités au sein d’un même environnement induit nécessairement des problèmes de ordonnancement =⇒non-déterminisme des exécutions partage des données visibilité des données synchronisation des actions sur ces données Arnaud Labourel, [email protected] Les threads en Java Conclusion Provisoire La programmation multi-thread est délicate : non-déterminisme, variables partagée et synchronisation, optimisations provoquant des comportements parfois non intuitifs, portabilité vs utilisation des ressources natives, variations dans l’implémentation des machines virtuelles, erreurs ne se révélant que sous hautes charges (ie en production)... Méthodologie particulière à suivre en utilisant les nombreuses primitives de synchronisation (apparues depuis la version 1.5). Arnaud Labourel, [email protected] Les threads en Java