Programmation concurrente et temps rel en Java Badr
Programmation concurrente et temps réel en Java Badr Benmammar bbm@badr-benmammar. com
Cycle de vie d’un Thread class Bavarder. Et. Lancer. Le. Perroquet 4 { public static void main(String args[]) { Perroquet 4 perroquet = new blabla(); Perroquet 4("coco", 5); perroquet. start(); System. out. println ("Thread bavard : " + Thread. current. Thread(). get. Name()); for (int n=0; n<15; n++) { try { Thread. sleep(1000); } catch(Interrupted. Exception e) { } System. out. println("Thread perroquet is. Alive : " + perroquet. is. Alive()); } } private static void blabla() { System. out. println("blabla"); } } class Perroquet 4 extends Thread { private String cri = null; private int fois = 0; public Perroquet 4 (String s, int i) { cri = s; fois = i; } public void run() { System. out. println("Thread perroquet : " + Thread. current. Thread(). get. Name()); for (int n=0; n<fois; n++) { try { Thread. sleep(1000); } catch(Interrupted. Exception e) { } System. out. println(cri); } } }
Exécution: Cycle de vie d’un Thread bavard : main Thread perroquet : Thread-0 Thread perroquet is. Alive : true blabla coco Thread perroquet is. Alive : false blabla Thread perroquet is. Alive : false blabla Le Thread "principal", celui qui exécute main s'appelle main. L’autre, celui du perroquet, a reçu comme nom par défaut Thread-0. La méthode is. Alive() détermine si un Thread est en train d'exécuter sa méthode run. La méthode de classe current. Thread() retourne un pointeur sur l’objet Thread qui appelle cette méthode. La méthode get. Name() donne le nom du Thread.
Propriétés des différents Threads class Bavarder. Et. Lancer. Le. Perroquet 5 { public static void main(String args[]) { Thread. current. Thread(). set. Name("bavard"); Perroquet 5 perroquet = new Perroquet 5("coco", 15); perroquet. start(); for (int n=0; n<5; n++) { try { Thread. sleep(1000); } catch(Interrupted. Exception e) { } blabla(); }} private static void blabla() { System. out. println("blabla"); }} class Perroquet 5 extends Thread { private String cri = null; private int fois = 0; public Perroquet 5(String s, int i) { super("perroquet"); cri = s; fois = i; } public void run(){ affiche. Threads(); for (int n=0; n<fois; n++) { try { Thread. sleep(1000); } catch(Interrupted. Exception e) { } System. out. println(cri); } affiche. Threads(); } private void affiche. Threads() { Thread[] tab. Thread = new Thread[Thread. active. Count()]; int nbr. Thread = Thread. enumerate(tab. Thread); for (int i = 0; i < nbr. Thread ; i++) System. out. println(i + "-ieme Thread : " + tab. Thread[i]. get. Name()); } }
Propriétés des différents Threads Exécution: 0 -ieme Thread : bavard 1 -ieme Thread : perroquet coco blabla coco blabla coco coco coco 0 -ieme Thread : perroquet 1 -ieme Thread : Destroy. Java. VM La méthode set. Name (String nom) permet de nommer le Thread. Le constructeur offre la possibilité de le nommer ainsi Thread (String nom). La méthode de classe active. Count () donne le nombre de Threads actifs dans le groupe de l'appelant. La méthode de classe enumerate (Thread[] tableau) stocke dans le tableau donné les références des Threads actifs dans le groupe de l'appelant et ses sous-groupes. Elle renvoie le nombre de Threads actifs obtenus. Remarquons que le Thread main, renommé bavard, a fini avant le Thread perroquet, et ne l'attend pas pour terminer.
Synchronisation sur terminaison class Bavarder. Et. Lancer. Le. Perroquet 6{ public static void main(String args[]) { Perroquet 6 perroquet = new Perroquet 6("coco", 10); perroquet. start(); for (int n=0; n<5; n++) blabla(); try { perroquet. join(); } catch(Interrupted. Exception e) { System. out. println(e. get. Message()); System. exit(2); } System. out. println("fin du Thread perroquet !"); for (int n=0; n<5; n++) blabla(); } private static void blabla() { System. out. println("blabla"); try { Thread. sleep(1000); } catch(Interrupted. Exception e) { } }} Exécution: class Perroquet 6 extends Thread { blabla private String cri = null; coco private int fois = 0; blabla public Perroquet 6 (String s, int i) { coco blabla super ("perroquet"); coco cri = s; blabla fois = i; } coco public void repeter() { coco System. out. println(cri); blabla coco try {Thread. sleep(1000); } coco catch(Interrupted. Exception e) { } coco public void run() { coco fin du Thread perroquet ! for (int n=0; n<fois; n++) blabla repeter(); blabla } blabla
Synchronisation sur terminaison Exécution: La méthode join() attend la terminaison du Thread spécifié. blabla coco blabla Si le Thread est créé mais pas "starté", il est considéré comme coco terminé ! blabla coco blabla join() peut aussi avoir un paramètre donné qui est un timeout coco maximal. coco perroquet. join(); blabla coco Les 2 Threads ne sont plus indépendants puisque le "main" coco attend, à une certaine étape, la terminaison (fin d'exécution) de coco l'autre. C'est une forme de synchronisation. coco fin du Thread perroquet ! Exécution en multitâche blabla Le main attend la terminaison du Thread perroquet blabla Le main termine son exécution blabla
Exemple de join() : synchroniser deux écrivains public class Ecrivain extends Thread { private String texte; public Ecrivain(String t) { texte=t; } public void run() { for (int i=0; i<10; i++) { int j=0; for (; j<texte. length()-1; j++) { System. out. print(texte. substring(j, j+1)); try { sleep((long)(Math. random() * 100)); } catch (Interrupted. Exception e) {} } System. out. println(texte. substring(j, j+1)); } System. out. println("ecrivain de " +texte+" a fini"); } } public class Prog 55 { public static void main (String argv[]) { Ecrivain ecrivain. A, ecrivain. B; ecrivain. A = new Ecrivain ("ABC"); ecrivain. B = new Ecrivain ("XYZ"); ecrivain. A. start(); ecrivain. B. start(); } } Exemple de substring : String date = "15/08/2000"; String jour = date. substring(0, 2); // donne 15 String mois = date. substring(3, 5); // donne 08 String annee = date. substring(6, 10); // donne 2000 Chaine du caractère 6 à 9 (premier caractère d’indice 0).
Exemple de join() : synchroniser deux écrivains Exécution: AXBYC ABZ XC AYZ XBC AYBC AZ XBC ABYZ XC AYBC AZ XBC ecrivain de ABC a fini YZ XYZ XYZ ecrivain de XYZ a fini public class Prog 55 { public static void main (String argv[]) { Ecrivain ecrivain. A, ecrivain. B; ecrivain. A = new Ecrivain ("ABC"); ecrivain. B = new Ecrivain ("XYZ"); ecrivain. A. start(); try { ecrivain. A. join(); } catch(Interrupted. Exception e) { System. out. println(e. get. Message()); System. exit(1); } ecrivain. B. start(); } } Exécution : ABC ABC ABC ecrivain de ABC a fini XYZ XYZ XYZ ecrivain de XYZ a fini
Deux Threads sans sleep class Bavarder. Et. Lancer. Le. Perroquet 7 { public static void main(String args[]) { Perroquet 7 perroquet = new Perroquet 7("coco", 10); perroquet. start(); for (int n=0; n<10; n++) blabla(); } private static void blabla() { System. out. println("blabla"); } } class Perroquet 7 extends Thread { private String cri = null; private int fois = 0; public Perroquet 7(String s, int i) { cri = s; fois = i; } public void repeter() { System. out. println(cri); } public void run() { for (int n=0; n<fois; n++) repeter(); } } Exécution: blabla blabla blabla coco coco coco Le temps d’exécution est trop court pour visualiser la répartition du CPU entre les 2 Threads.
Répartition de temps entre deux Threads public class Course. Infernale 1 { public static void main(String[] args) { Coureur A = new Coureur("A"); Coureur B = new Coureur("B"); A. start(); B. start(); }} class Coureur extends Thread { String nom; public Coureur(String nom) { super(nom); this. nom = nom; } public void run() { long coups. De. Pedale = 0; while (coups. De. Pedale < 5000000) { coups. De. Pedale++; if ((coups. De. Pedale % 500000) == 0) { System. out. println("Coureur " + nom + " a donne " + coups. De. Pedale + " coups de pedale. "); } } }}
Répartition de temps entre deux Threads Exécution: Coureur A a donne 500000 coups de pedale. Coureur B a donne 1000000 coups de pedale. Coureur A a donne 1000000 coups de pedale. Coureur B a donne 1500000 coups de pedale. Coureur A a donne 2000000 coups de pedale. Coureur B a donne 2000000 coups de pedale. Coureur A a donne 2500000 coups de pedale. Coureur B a donne 2500000 coups de pedale. Coureur A a donne 3000000 coups de pedale. Coureur B a donne 3000000 coups de pedale. Coureur A a donne 3500000 coups de pedale. Coureur B a donne 3500000 coups de pedale. Coureur A a donne 4000000 coups de pedale. Coureur B a donne 4000000 coups de pedale. Coureur A a donne 4500000 coups de pedale. Coureur B a donne 4500000 coups de pedale. Coureur A a donne 5000000 coups de pedale. Coureur B a donne 5000000 coups de pedale. Java n'impose pas que le système soit "time-sliced" : cad que la même quantité de temps soit impartie aux Threads de même niveau de priorité.
Priorité entre Threads q La méthode set. Priority fixe le niveau de priorité entre les différents Threads. q La valeur doit être comprise entre une valeur minimale, MIN_PRIORITY, et maximale, MAX_PRIORITY. class Coureur extends Thread { String nom; public Coureur(String nom) { public class Course. Infernale 2 { super(nom); public static void main(String[] args) { this. nom = nom; } Coureur A = new Coureur("A"); public void run() { Coureur B = new Coureur("B"); long coups. De. Pedale = 0; A. set. Priority(Thread. MAX_PRIORITY); while (coups. De. Pedale < 5000000) { B. set. Priority(Thread. MIN_PRIORITY); coups. De. Pedale++; System. out. println("Thread Coureur " + if ((coups. De. Pedale % 500000) == 0) { A. nom + " a la priorite = " + A. get. Priority()); System. out. println("Coureur " + nom System. out. println("Thread Coureur " + + " a donne " + coups. De. Pedale + B. nom + " a la priorite = " + B. get. Priority()); " coups de pedale. "); A. start(); } B. start(); } } }
Priorité entre Threads Exécution: Thread Coureur A a la priorite = 10 Thread Coureur B a la priorite = 1 Coureur A a donne 500000 coups de pedale. Coureur A a donne 1000000 coups de pedale. Coureur A a donne 1500000 coups de pedale. Coureur A a donne 2000000 coups de pedale. Coureur A a donne 2500000 coups de pedale. Coureur A a donne 3000000 coups de pedale. Coureur A a donne 3500000 coups de pedale. Coureur A a donne 4000000 coups de pedale. Coureur A a donne 4500000 coups de pedale. Coureur A a donne 5000000 coups de pedale. Coureur B a donne 500000 coups de pedale. Coureur B a donne 1000000 coups de pedale. Coureur B a donne 1500000 coups de pedale. Coureur B a donne 2000000 coups de pedale. Coureur B a donne 2500000 coups de pedale. Coureur B a donne 3000000 coups de pedale. Coureur B a donne 3500000 coups de pedale. Coureur B a donne 4000000 coups de pedale. Coureur B a donne 4500000 coups de pedale. Coureur B a donne 5000000 coups de pedale.
Méthode yield() q La méthode yield() "rend le processeur" : elle indique au contrôleur d'exécution des Threads d'en choisir un nouveau à exécuter (donc ca pourrait être le même !). Elle est intéressante dans peu de cas. class Bavarder. Et. Lancer. Le. Perroquet 8 { public static void main(String args[]) { Perroquet 8 perroquet = new Perroquet 8("coco", 10); perroquet. start(); for (int n=0; n<10; n++) { blabla(); Thread. current. Thread(). yield(); } } private static void blabla() { System. out. println("blabla"); } } class Perroquet 8 extends Thread { private String cri = null; private int fois = 0; public Perroquet 8(String s, int i) { cri = s; fois = i; } public void repeter() { System. out. println(cri); } public void run() { for (int n=0; n<fois; n++) { repeter(); yield(); } } }
Essayer de Stopper l’exécution d’un Thread class Lancer. Et. Arreter. Le. Perroquet 9 { class Perroquet 9 extends Thread { public static void main(String args[]) { private String cri = null; Perroquet 9 perroquet = new public Perroquet 9(String s) { Perroquet 9("coco"); cri = s; perroquet. start(); } String reponse="o"; public void repeter() { do {System. out. println("voulez-vous que le System. out. println(cri); perroquet continue ? (o/n)"); try { Thread. current. Thread(). yield(); Thread. sleep((int)Math. random()*1000); reponse = Saisie. litexte(); } } while (reponse. equals("o")); catch(Interrupted. Exception e) {} }} } Exécution: public void run() { coco while (true) { coco repeter(); coco yield(); } }} coco … L’arrêt de l'exécution du Thread main n'entraine pas l'arrêt du Thread perroquet. L'arrêt brutal d’un Thread pouvait laisser des objets dans des états inconsistants (par exemple, des verrous pouvaient avoir été posés). C'est au programmeur de prévoir quand (et donc comment) l'exécution du Thread peut s'arrêter sans risque.
Stopper proprement : mot clé volatile class Lancer. Et. Arreter. Le. Perroquet 10{ public static void main(String args[]) { Perroquet 10 perroquet = new Perroquet 10("coco"); perroquet. start(); String reponse="o"; do { System. out. println("voulez-vous que le perroquet continue ? (o/n)"); reponse = Saisie. litexte(); } while (reponse. equals("o")); perroquet. stopper(); } } class Perroquet 10 extends Thread{ private volatile boolean continuer = true; private String cri = null; public Perroquet 10(String s) { continuer = true; cri = s; } public void repeter() { System. out. println(cri); try { Thread. sleep((int)Math. random()*2000); } catch(Interrupted. Exception e) { } } public void stopper() { continuer = false; } public void run() { while (continuer) { repeter(); } } }
Stopper proprement Exécution : coco coco coco ncoco coco La solution est très simple : une variable booléenne sert dans la méthode run pour savoir s’il faut continuer ou arrêter. Cette variable doit être déclarée volatile. Pour une variable, le modificateur volatile force la JVM, avant et après chaque utilisation de la variable par un Thread , à la rafraîchir à partir de la mémoire principale au lieu d'utiliser un cache local. Cela permet de synchroniser la valeur de la variable entre plusieurs Threads.
Programmer une tâche en précisant un délai initial import java. util. Timer. Task; import java. util. Timer; class Declancher. Le. Perroquet 11{ public static void main(String args[]) { Perroquet 11 perroquet = new Perroquet 11("coco", 3); Timer timer = new Timer(); timer. schedule(perroquet, 4000); String reponse="oui"; do { System. out. println("blabla"); System. out. println("voulez-vous encore bavarder ? (o/n)"); reponse = Saisie. litexte(); } while (reponse. equals("o")); timer. cancel(); } } class Perroquet 11 extends Timer. Task { private String cri = null; private int fois = 0; public Perroquet 11(String s, int i) { cri = s; fois = i; } public void repeter() { System. out. println(cri); } public void run() { for (int i = 0; i < fois; i++) { repeter(); } } }
Timer. Task public abstract class Timer. Task extends Object implements Runnable q Timer. Task est une classe abstraite qui implémente Runnable, donc une méthode run(), il faut donc hériter de la classe Timer. Task et redéfinir la méthode run() qui code la tâche à effectuer. q Un Timer permet de déclencher l'exécution de tâches une ou plusieurs fois en précisant un délai initial et/ou une périodicité. Plusieurs tâches peuvent être programmées selon des programmes divers.
Programmer une tâche en précisant un délai initial Exécution: blabla voulez-vous encore bavarder ? (o/n) o blabla voulez-vous encore bavarder ? (o/n) coco o blabla voulez-vous encore bavarder ? (o/n) n A un Timer correspond un Thread qui exécutera successivement les tâches à effectuer. Les tâches sont des Timer. Task et doivent être courte. Aucune garantie de temps réel n'est assurée par ce mécanisme. schedule(tache, long millisecondes) programme la tâche en précisant un délai initial en millisecondes. La méthode cancel() arrête la programmation du Timer.
Programmer une tâche avec délai initial et une périodicité import java. util. Timer. Task; class Perroquet 11 extends Timer. Task { import java. util. Timer; private String cri = null; class Declancher. Le. Perroquet 11{ private int fois = 0; public static void main(String args[]) { public Perroquet 11(String s, int i) { Perroquet 11 perroquet = new Perroquet 11("coco", 3); cri = s; Timer timer = new Timer(); fois = i; timer. schedule(perroquet, 3000, 2000); } String reponse="oui"; public void repeter() { do { System. out. println(cri); System. out. println("blabla"); } System. out. println("blabla"); public void run() { System. out. println("voulez-vous encore for (int i = 0; i < fois; i++) { bavarder ? (o/n)"); repeter(); reponse = Saisie. litexte(); } } while (reponse. equals("o")); } timer. cancel(); } } schedule(tache, long delai, long période) programme la tâche après un délai pour } une exécution périodiques : les temps sont donnés en millisecondes.
Programmer une tâche avec délai initial et une périodicité Exécution: blabla voulez-vous que le perroquet continue ? (o/n) coco coco ncoco
Partager une ressource: imprimeur public class Imprimeur 1 { private String texte; public Imprimeur 1() { texte=""; } public void imprimer(String t) { texte=t; for (int j=0; j<texte. length()-1; j++) { System. out. print(texte. substring(j, j+1)); try { Thread. sleep(100); } catch (Interrupted. Exception e) {}; } System. out. println (texte. substring(texte. length()-1, texte. length())); }} public class Prog 56 { public static void main (String argv[]) { Ecrivain 2 ecrivain. A, ecrivain. B; Imprimeur 1 imprim= new Imprimeur 1(); ecrivain. A = new Ecrivain 2("ABC", imprim); ecrivain. B = new Ecrivain 2("XYZ", imprim); ecrivain. A. start(); ecrivain. B. start(); }} public class Ecrivain 2 extends Thread { private String texte; private Imprimeur 1 imprim; public Ecrivain 2(String t, Imprimeur 1 i) { imprim=i; texte=t; } public void run() { for (int i=0; i<10; i++) { imprimer(texte); try { sleep((long)(Math. random() * 100)); } catch (Interrupted. Exception e) {} } System. out. println("ecrivain de " +texte+" a fini"); } }
Partager une ressource: imprimeur Exécution: AXYYZ Z AXYYZ Les écrivains passent par un imprimeur pour écrire. Z AXYYZ AC Les 2 écrivains s'adressent maintenant à un imprimeur commun. XYYZ Z XABBC Non seulement, les écrivains écrivent en "interleaving" (entrelacement), C mais ils "écrasent" la variable texte de l'imprimeur. AXYYZ Z AXYYZ L'ensemble est encore illisible. Z XABBC C XABBC ecrivain de XYZ a fini C ecrivain de ABC a fini
Ressource en exclusion mutuelle public class Imprimeur 1 { private String texte; public Imprimeur 1() { texte=""; } public synchronized void imprimer(String t) { texte=t; for (int j=0; j<texte. length()-1; j++) { System. out. print(texte. substring(j, j+1)); try { Thread. sleep(100); } catch (Interrupted. Exception e) {}; } System. out. println (texte. substring(texte. length()-1, texte. length())); } }
Ressource en exclusion mutuelle Exécution : ABC XYZ ABC synchronized définit un verrou/ une section en exclusion XYZ ABC mutuelle sur la méthode imprimer : Un seul Thread au plus XYZ peut exécuter la méthode à la fois. ABC XYZ ABC Xecrivain de ABC a fini YZ ecrivain de XYZ a fini
Synchronisation des threads
Plan q Variables partagées q Problème de l’exclusion mutuelle q Bloc synchronisé q Méthode d’instance synchronisée q Problème de coopération des threads q wait, notify. All et notify q Demi-synchronisation (wait et sleep) q Problème du Producteur et du Consommateur q Interblocage
Variables partagées class Perroquets. Matheux 20 { private int compteur; public static void main(String args[]) { new Perroquets. Matheux 20(); } public Perroquets. Matheux 20 () { compteur = 1; Perroquet 20 perroquet. A = new Perroquet 20("coco", 10); Perroquet 20 perroquet. B = new Perroquet 20("bonjour", 10); perroquet. A. start(); perroquet. B. start(); try { perroquet. A. join(); perroquet. B. join(); } catch(Interrupted. Exception e) { } System. out. println("compteur = "+compteur); } class Perroquet 20 extends Thread { private String cri = null; private int fois = 0; public Perroquet 20 (String s, int i) { cri = s; fois = i; } public void repeter() { String repete = cri + " " + compteur; System. out. println(repete); compteur++; try { Thread. sleep((int)(Math. random()*1000)); } catch(Interrupted. Exception e) { } } public void run(){ for (int n=0; n<fois; n++) repeter(); } }}
Variables partagées Exécution: coco 1 bonjour 2 bonjour 3 coco 4 bonjour 5 coco 6 bonjour 7 bonjour 8 bonjour 9 coco 10 bonjour 11 coco 12 bonjour 13 coco 14 coco 15 bonjour 16 coco 17 bonjour 18 coco 19 coco 20 compteur = 21 La classe Perroquet 20 se situe à l’intérieur de la classe Perroquets. Matheux 20, et la méthode join est utilisée afin de ne pas afficher la valeur du compteur avant la terminaison des deux Threads. Du fait des règles de visibilité de Java, la variable compteur est visible/accessible à partir de la classe Perroquet 20 donc des 2 objets threads perroquet. A et perroquet. B, par contre, les variables d'instance cri et fois de Perroquet 20 existent en autant d'exemplaires que d'instances de Perroquet 20. Les 2 threads accède donc à un espace partagé/commun de variables. Contrairement au processus qui possède son propre espace de travail clairement séparé des autres processus, les threads sont exécutés au sein du même processus "java".
Problème de l’accès concurrent (partage de ressource) class Perroquets. Matheux 21{ private int compteur; public static void main(String args[]) { new Perroquets. Matheux 21(); } public Perroquets. Matheux 21() { compteur = 0; Perroquet 21 perroquet. A = new Perroquet 21("coco", 10); Perroquet 21 perroquet. B = new Perroquet 21("bonjour", 10); //perroquet. A. set. Priority(perroquet. B. get. Priority()%2 ); perroquet. A. start(); perroquet. B. start(); try { perroquet. A. join(); perroquet. B. join(); } catch(Interrupted. Exception e) { } System. out. println("compteur = "+compteur); } class Perroquet 21 extends Thread { private String cri = null; private int fois = 0; public Perroquet 21(String s, int i) { cri = s; fois = i; } public void repeter() { int valeur = compteur + 1; String repete = cri + " " + valeur; System. out. println(repete); try { Thread. sleep((int)(Math. random()*100)); } catch(Interrupted. Exception e) { } compteur = valeur; try {Thread. sleep((int)(Math. random()*100)); } catch(Interrupted. Exception e) { } } public void run(){ for (int n=0; n<fois; n++) repeter(); } } }
Problème de l’accès concurrent (partage de ressource) Exécution: coco 1 bonjour 1 coco 2 coco 3 bonjour 3 coco 4 bonjour 4 coco 5 bonjour 5 coco 6 bonjour 7 coco 7 bonjour 8 coco 8 bonjour 9 coco 10 bonjour 11 bonjour 12 compteur = 12 Les 2 threads perroquet travaillent alternativement : un thread peut être suspendu au milieu de l’exécution de sa méthode repeter pour que le contrôleur de thread laisse l’autre s'exécuter.
Définir une section critique : Bloc synchronisé class Perroquets. Matheux 22{ private Compteur compteur; public static void main(String args[]) { new Perroquets. Matheux 22(); } public Perroquets. Matheux 22() { compteur = new Compteur(); Perroquet 22 perroquet. A = new Perroquet 22("coco", 10); Perroquet 22 perroquet. B = new Perroquet 22("bonjour", 10); perroquet. A. set. Priority(perroquet. B. get. Priority()%2); perroquet. A. start(); perroquet. B. start(); try { perroquet. A. join(); perroquet. B. join(); } catch(Interrupted. Exception e) { } System. out. println("compteur = "+compteur. get. Valeur()); } class Perroquet 22 extends Thread { private String cri = null; private int fois = 0; public Perroquet 22(String s, int i) { cri = s; fois = i; } public void repeter() { synchronized (compteur) { int valeur = compteur. get. Valeur() + 1; String repete = cri + " " + valeur; System. out. println(repete); try { Thread. sleep((int)(Math. random()*100)); } catch(Interrupted. Exception e) { } compteur. set. Valeur(valeur); } try { Thread. sleep((int)(Math. random()*100)); } catch(Interrupted. Exception e) { } } public void run(){ for (int n=0; n<fois; n++) repeter(); } } class Compteur { private int valeur = 0; public int get. Valeur() { return valeur; } public void set. Valeur(int v) { valeur = v; } } }
Exécution: bonjour 1 coco 2 bonjour 3 coco 4 bonjour 5 coco 6 coco 7 bonjour 8 coco 9 bonjour 10 bonjour 11 coco 12 coco 13 bonjour 14 coco 15 bonjour 16 bonjour 17 coco 18 coco 19 bonjour 20 compteur = 20 Définir une section critique : Bloc synchronisé q Le mot-clé synchronized définit un bloc d'instruction qui ne peut s'exécuter qu'exclusivement même si plusieurs threads souhaitent l'exécuter : q Lorsque le thread perroquet. A exécute ce bloc synchronisé, et que le thread perroquet. B souhaite commencer l'exécution de ce même bloc, alors le thread perroquet. B doit attendre. Quand le thread perroquet. A aura finit, le thread perroquet. B pourra reprendre. q Seul un thread à la fois peut exécuter un bloc synchronisé. q On dit que le bloc est en exclusion mutuelle ou encore que c'est une section critique. q Les autres threads, s'ils désirent exécuter cette section, doivent attendre que le thread en section critique la termine. q Si plusieurs threads attendent pour un même bloc synchronisé qui "se libère", le contrôleur de thread n'en autorisera qu'un à l'exécuter. q L'appel à sleep() ne provoque pas de sortie de la section critique.
Bloc synchronisé
Bloc synchronisé synchronized(objet) signifie que le bloc est en exclusion mutuelle relativement à un moniteur (monitor) de cet objet : sont en exclusion mutuelle, les threads synchronisés sur le même objet. Le thread de gauche et celui du milieu ont une section critique mutuelle, le thread de droite a une section critique mais pas avec les 2 autres threads. Le moniteur "tient" le rôle de superviseur s'assurant que seul un thread à la fois peut exécuter la section critique qu'il supervise : c'est un système de verrouillage (lock). Le thread qui exécute synchronized d'un objet devient propriétaire du moniteur de cet objet. Thread. sleep ne fait pas perdre la propriété d'un moniteur même temporairement. Il n'est pas souhaitable de mettre un sleep(délai) dans une zone synchronisée : on préfèrera wait(timeout).
Méthode d’instance synchronisée public void repeter() { class Perroquets. Matheux 23 { int valeur = compteur. plus 1(); private Compteur compteur; public static void main(String args[]) { String repete = cri + " " + valeur; new Perroquets. Matheux 23(); System. out. println(repete); } try { public Perroquets. Matheux 23() { Thread. sleep((int)(Math. random()*100)); compteur = new Compteur(); } Perroquet 23 perroquet. A = new Perroquet 23("coco", 10); catch(Interrupted. Exception e) { } Perroquet 23 perroquet. B } = new Perroquet 23("bonjour", 10); public void run(){ perroquet. A. set. Priority(perroquet. B. get. Priority()%2); for (int n=0; n<fois; n++) perroquet. A. start(); repeter(); perroquet. B. start(); try { } perroquet. A. join(); } perroquet. B. join(); } class Compteur catch(Interrupted. Exception e) { } { System. out. println("compteur = "+compteur. valeur); } private int valeur = 0; class Perroquet 23 extends Thread { public synchronized int plus 1() { private String cri = null; return ++valeur; private int fois = 0; } public Perroquet 23(String s, int i) { } cri = s; fois = i; } }
Méthode d’instance synchronisée Exécution : coco 1 bonjour 2 bonjour 3 coco 4 coco 5 bonjour 6 bonjour 7 coco 8 bonjour 9 coco 10 bonjour 11 coco 12 bonjour 13 bonjour 14 coco 15 bonjour 16 coco 17 bonjour 18 coco 19 coco 20 compteur = 20 Le mot synchronised définit le bloc de la méthode en exclusion mutuelle içi c'est l'objet compteur qui "monitorise", la méthode synchronisée est aussi un mécanisme d'exclusion mutuelle sur une portion de code : le moniteur qui supervise cette section critique est celui de l'objet sur lequel est appelée la méthode. remarque : synchronized méthode(paramètres) { bloc d'instructions } est équivalent à : méthode(paramètres) { synchronized(this) { bloc d'instructions } } La synchronisation ralentit l'ensemble de l'exécution, donc il faut limiter le nombre de portion synchronisée et leur taille (en instructions), il est possible de synchroniser sur une classe pour accéder en exclusion mutuelle sur les variables de classe, idem pour une méthode de classe synchronisée. Le mécanisme de "monitor" d'un objet s'applique à toutes les instances de Object : c'est donc un mécanisme implémenté au coeur de JAVA, un seul thread peut être à la fois le propriétaire du moniteur d'un objet.
Rappel
Multitâche q Un système d’exploitation est dit multitâche ou à temps partagé lorsque plusieurs «tâches» (processus) peuvent être exécutées simultanément. q 2 types de système d’exploitation multitâche : q Multitâche coopératif (non-préemptif) : q Une forme simple de multitâche où chaque processus doit explicitement permettre à une autre tâche de s’exécuter. Cette approche simplifie l’architecture du système d’exploitation mais présente plusieurs inconvénients : q Si un des processus ne redonne pas la main à un autre processus, par exemple si le processus est bugué, le système entier peut s’arrêter. q Multitâche préemptif : q Pour remédier à cette situation, les systèmes ont évolué pour utiliser une approche nommée « multitâche préemptif » . Dans un tel système, le processeur signale au système d’exploitation que le processus en cours d’exécution doit être mis en pause pour permettre l’exécution d’un autre processus. q Ne pas attendre des heures qu’un programme planté cède la priorité.
Processus vs Thread q La plupart des systèmes d’exploitation offrent la distinction entre : q q Processus lourd : q Un programme (un ensemble d’instructions) à exécuter. q Sont complètement isolés les uns des autres. Processus léger : Thread q Portion de code capable de s’exécuter en parallèle à d’autres traitements. q Ils partagent code, données et ressources. q Mais peuvent disposer de leurs propres données.
Création de thread q 2 manières pour créer un Thread : q Une classe qui dérive de java. lang. Thread. q java. lang. Thread implémente Runnable. q Il faut redéfinir la méthode run(). q Une classe qui implémente l’interface Runnable q Il faut implémenter la méthode run()
Méthode 1 : Sous-classer Thread class Thread 1 extends Thread { Thread 1() {. . . } // Le constructeur. . . public void run() {. . . // Ici ce que fait le thread } }. . . Thread 1 p 1 = new Thread 1(); // Création du thread p 1. start(); // Démarre le thread et exécute p 1. run()
Méthode 2 : une classe qui implémente Runnable class Thread 2 implements Runnable { Thread 2() {. . . } // Constructeur. . . public void run() {. . . // Ici ce que fait le thread } }. . . Thread 2 p = new Thread 2(); Thread p 2 = new Thread(p); . . . p 2. start(); // Démarre le thread et exécute p. run()
Quelle solution choisir ? q Méthode 1 : sous-classer Thread q Lorsqu’on désire paralléliser une classe qui n’hérite pas déjà d’une autre classe (classe autonome). q Attention : héritage simple. q Méthode 2 : implémenter Runnable q Lorsqu’une super-classe est imposée. q Cas des applets public class My. Thread. Applet extends Applet implements Runnable {}
Applet et thread q Un thread qui conte de 1 à 20, il fait l’affichage à la fois dans l’applet et sur la console. import java. applet. *; import java. awt. *; public class Counter. Thread extends Applet implements Runnable { Thread t; int Count; public void init() { Count=0; t=new Thread(this); // t doit prendre un objet this comme paramètre t. start(); } public void run() { while (Count < 20) { Count++; repaint(); try { t. sleep(1000); } catch (Interrupted. Exception e) {} } } public void paint(Graphics g) { g. draw. String(Integer. to. String(Count), 10); System. out. println("Count= "+Count); } }
Thread. Group q Thread. Group : dans java. lang. q Plusieurs Threads peuvent s’exécuter en même temps, il serait utile de pouvoir les manipuler comme une seule entité. q Pour les suspendre, q Pour les arrêter, . . . q Java offre cette possibilité via l’utilisation des groupes de threads : java. lang. Thread. Group. q On groupe un ensemble nommé de threads. q Ils sont contrôlés comme une seule unité. q La JVM crée au minimum un groupe de threads nommé main. q Par défaut, un thread appartient au même groupe que celui qui l’a crée (son père). q get. Thread. Group() : pour connaitre son groupe.
Création d’un groupe de threads q Pour créer un groupe de threads : Thread. Group groupe 1 = new Thread. Group("GP 1"); Thread p 1 = new Thread(groupe 1, "P 1"); Thread p 2 = new Thread(groupe 1, "P 2"); Thread p 3 = new Thread(groupe 1, "P 3"); q Le contrôle des Thread. Group passe par l’utilisation des méthodes standards qui sont partagées avec Thread : q Exemple : interrupt(), destroy(), q Par exemple : appliquer la méthode interrupt() à un Thread. Group revient à invoquer pour chaque Thread du groupe cette même méthode.
Création d’une arborescence de threads Thread. Group groupe 1 = new Thread. Group("GP 1"); Thread p 1 = new Thread(groupe 1, "P 1"); Thread p 2 = new Thread(groupe 1, "P 2"); Thread p 3 = new Thread(groupe 1, "P 3"); Thread. Group groupe 11 = new Thread. Group(groupe 1, "GP 11"); Thread p 4 = new Thread(groupe 11, "P 4"); Thread p 5 = new Thread(groupe 11, "P 5");
Synchronisation des threads q Premier type de synchronisation : q Synchronisation compétitive : lorsque plusieurs threads utilisent la même ressource (exemple : imprimeur, l’accès concurrents à un compte en banque). q Définir un verrou / une section en exclusion mutuelle. q En Java, chaque objet possède un moniteur (superviseur) qui peut garder les sections critiques. Il assure que seul un thread à la fois peut exécuter la section critique qu’il supervise. q synchronized (objet) { instr } moniteur associé à objet. q synchronized void Methode(){. . } moniteur associé à la méthode. q L’objet est verrouillé pendant que le bloc synchronisé est exécuté. synchronized (objet) T 2 Queue d’attente T 3 T 4 T 1 est entrain d’exécuter le bloc synchronisé En Java, chaque objet possède une queue d’attente
Moniteur associe à un objet 1 seul moniteur
Moniteurs associent à 2 objets 2 moniteurs Le thread qui exécute synchronized d’un objet devient propriétaire du moniteur de cet objet
Synchronisation des threads q Deuxième type de synchronisation : q Synchronisation coopérative : lorsqu’un thread attend la fin de l’exécution d’un autre avant de poursuivre son exécution. q Priorités entre threads : méthode set. Priority(entier). q Les priorités des threads se situent dans un intervalle de 1 à 10. q Trois constantes représentent les valeurs limites et médianes. q Plus la valeur de la priorité est élevée, plus grande sera la priorité du thread pour l'accès au processeur. q q q MIN_PRIORITY NORM_PRIORITY MAX_PRIORITY : priorité minimum (1) : priorité normale (5) : priorité maximum (10) q La priorité normale est affectée par défaut à un nouveau thread. Coureur A = new Coureur("A"); Coureur B = new Coureur("B"); A. set. Priority(Thread. MAX_PRIORITY); B. set. Priority(Thread. MIN_PRIORITY);
Synchronisation des threads q Méthode join() : synchroniser deux écrivains ecrivain. A = new Ecrivain ("ABC"); ecrivain. B = new Ecrivain ("XYZ"); ecrivain. A. start(); try { ecrivain. A. join(); } catch(Interrupted. Exception e) {} ecrivain. B. start(); q Autre type de coopération avec : wait, notify. All.
Exemple de coopération des threads (Professeur vs élèves) Un professeur qui apprends à des élèves des nouveaux mots. Les élèves doivent répéter chaque mot. Parler Maison Bonjour Discuter. . . Thread principal Maison 2 Threads : 2 élèves
Problème de coopération des threads public class Ecole. Des. Perroquets 14 { static String mot = null; public static void main(String[] args) { Perroquet 14 perroquet 1 = new Perroquet 14("coco"); perroquet 1. start(); Perroquet 14 perroquet 2 = new Perroquet 14("jaco"); perroquet 2. start(); String reponse = null; do { mot = reponse; System. out. println ("nouveau mot pour perroquet ? (sinon non)"); reponse = Saisie. litexte(); } while (! reponse. equals("non")); System. exit(1); } } class Perroquet 14 extends Thread { private String nom; public Perroquet 14(String n) { super(n); nom = n; } public void repeter() { System. out. println(nom + " "+ Ecole. Des. Perroquets 14. mot); } public void run() { while (true) { while (Ecole. Des. Perroquets 14. mot == null) try {Thread. sleep(2000); } catch (Exception e) { } for (int n=0; n<3; n++) repeter(); try{Thread. sleep((int)(Math. random()*3000)); } catch(Interrupted. Exception e) { } }
Problème de coopération des threads Exécution: nouveau mot pour perroquet ? (sinon non) bla nouveau mot pour perroquet ? (sinon non) coco bla jaco bla blu nouveau mot pour perroquet ? (sinon non) coco blu blo coco blu coco blu nouveau mot pour perroquet ? (sinon non) jaco blo jaco blo. . . Une variable static "mot" est partagée entre les 2 threads perroquets qui doivent l'apprendre puis le répéter et le thread main qui en saisit un nouveau. L'ensemble n'est pas du tout synchronisée : les perroquets ne savent pas si le mot est un nouveau à apprendre ou si c'est l'ancien. Au début, ils doivent attendre avec une boucle pour le premier mot et si le user/professeur fournit trop rapidement des nouveaux mots, les 2 perroquets peuvent "en rater" ! Le but : Il faudrait avoir un mécanisme d'attente entre le professeur (user) et les élèves perroquets : les élèves attendent un nouveau mot à apprendre, le professeur, quand il enseigne un nouveau mot, devrait attendre que les élèves aient le temps de l'apprendre et le répéter, avant d'en enseigner un nouveau.
public class Ecole. Des. Perroquets 15 { public static void main(String[] args) { Au. Tableau autableau = new Au. Tableau(); Perroquet 15 perroquet 1 = new Perroquet 15("coco", autableau); perroquet 1. start(); Perroquet 15 perroquet 2 = new Perroquet 15("jaco", autableau); perroquet 2. start(); String reponse = "bonjour"; do {autableau. enseigner(reponse); System. out. println("nouveau mot pour perroquet ? (sinon non)"); reponse = Saisie. litexte(); } while (! reponse. equals("non")); System. exit(1); }} class Perroquet 15 extends Thread { private String cri; private String nom; private Au. Tableau autableau; public Perroquet 15(String n, Au. Tableau a) { super(n); nom = n; autableau = a ; cri = ""; } Coopération avec : wait et notify. All public void repeter() { System. out. println(nom + " "+ cri); } public void run() { while (true) { cri = autableau. apprendre(); for (int n=0; n<3; n++) repeter(); } } } Utiliser par les élèves class Au. Tableau { private String mot. Aapprendre = null; synchronized String apprendre() { try { wait(); } catch (Exception e) {} return mot. Aapprendre; } synchronized void enseigner (String mot) { mot. Aapprendre = mot ; notify. All(); } } Utiliser par le professeur
Coopération avec : wait et notify. All Exécution: nouveau mot pour perroquet ? (sinon non) miam nouveau mot pour perroquet ? (sinon non) coco miam jaco miam encore coco encore jaco encore nouveau mot pour perroquet ? (sinon non) non
Coopération avec : wait et notify. All q Les trois méthodes (wait, notify et notify. All) sont définies dans la classe java. lang. Object et sont donc héritées par toute classe. q La méthode wait suspend (bloque) l'exécution d'un thread, en attendant qu'une certaine condition soit réalisée. La réalisation de cette condition est signalée par un autre thread par la méthode notify ou notify. All. q Lorsque la méthode wait est invoquée à partir d'une méthode synchronized, en même temps que l'exécution est suspendue, le verrou posé sur l'objet par lequel la méthode a été invoquée est relâché. Dès que la condition de réveil survient, le thread attend de pouvoir reprendre le verrou et continuer l'exécution. q Une autre version de wait prend en argument un entier de type long qui définit la durée d’attente maximale (en millisecondes). q Si ce temps est dépassé, le thread est réveillé. q Différence entre sleep et wait : q sleep bloque l’exécution mais le thread garde le verrou (termine son exécution). q wait bloque l’exécution mais le thread libère le verrou (un deuxième thread peut exécuter la section critique).
Coopération avec : wait et notify. All
Coopération avec : wait et notify. All q Désormais, les perroquets attendent (wait) que le user/professeur leurs enseigne un nouveau mot et le note au tableau. Dès qu'il a noté le mot au tableau, le professeur indique (notify. All) aux perroquets en attente qu’ils peuvent le lire et le répéter. q La méthode wait() d'un objet doit se trouver dans un bloc "synchronized" sur ce même objet, elle doit acquérir le verrou de l’objet. synchronized (objet) {. . . objet. wait(); . . . } q Indique au moniteur de l’objet qu'elle se met en attente (comme sleep), mais (contrairement à sleep), elle libère le verrou sur l’objet, elle termine la phase de section critique. Néanmoins, quand le thead sera réveillé/débloqué, si plusieurs thread attendaient, un seul à la fois exécutera le reste du bloc en section critique.
Coopération avec : wait et notify. All q La méthode notify. All() d'un objet doit se trouver dans un bloc "synchronized" sur ce même objet, elle doit acquérir le verrou de l'objet. synchronized (objet) {. . . objet. notify. All(); . . . } q Indique au moniteur de l’objet que tous les threads en attente (wait) sur l'objet doivent être réveillés.
public class Ecole. Des. Perroquets 16 { public static void main(String[] args) { Au. Tableau autableau = new Au. Tableau(); Perroquet 16 perroquet 1 = new Perroquet 16("coco", autableau); perroquet 1. start(); Perroquet 16 perroquet 2 = new Perroquet 15("jaco", autableau); perroquet 2. start(); String reponse = "bonjour"; do {autableau. enseigner(reponse); System. out. println("nouveau mot pour perroquet ? (sinon non)"); reponse = Saisie. litexte(); } while (! reponse. equals("non")); System. exit(1); }} class Perroquet 16 extends Thread { private String cri; private String nom; private Au. Tableau autableau; public Perroquet 15(String n, Au. Tableau a) { super(n); nom = n; autableau = a ; cri = ""; } notify public void repeter() { System. out. println(nom + " "+ cri); } public void run() { while (true) { cri = autableau. apprendre(); for (int n=0; n<3; n++) repeter(); } } } class Au. Tableau { private String mot. Aapprendre = null; synchronized String apprendre() { try {wait(); } catch (Exception e) {} return mot. Aapprendre; } synchronized void enseigner (String mot) { mot. Aapprendre = mot ; notify(); } }
Exécution: coco bonjour miam coco miam nouveau mot pour perroquet ? (sinon non) encore jaco encore nouveau mot pour perroquet ? (sinon non) bizarre coco bizarre nouveau mot pour perroquet ? (sinon non) vraiment jaco vraiment nouveau mot pour perroquet ? (sinon non) non notify Un seul therad est libéré de l’attente par notify. Un seul perroquet élève apprend à la fois. notify() ne réveille qu'un thread à la fois, il n'y a pas de spécification sur le thread choisi ! c'est le contrôleur de thread qui choisit.
public class Ecole. Des. Perroquets 17 { public static void main(String[] args) { Au. Tableau autableau = new Au. Tableau(); Perroquet 15 perroquet 1 = new Perroquet 15("coco", autableau); perroquet 1. start(); Perroquet 15 perroquet 2 = new Perroquet 15("jaco", autableau); perroquet 2. start(); String reponse = "bonjour"; do {autableau. enseigner(reponse); System. out. println("nouveau mot pour perroquet ? (sinon non)"); reponse = Saisie. litexte(); } while (! reponse. equals("non")); System. exit(1); }} class Perroquet 17 extends Thread { private String cri; private String nom; private Au. Tableau autableau; public Perroquet 15(String n, Au. Tableau a) { super(n); nom = n; autableau = a ; cri = ""; } Demi-synchronisation (wait et sleep) public void repeter() { System. out. println(nom + " "+ cri); } public void run() { while (true) {cri = autableau. apprendre(); for (int n=0; n<3; n++) { repeter(); try {Thread. sleep(2000); } catch(Interrupted. Exception e) { } } } class Au. Tableau { private String mot. Aapprendre = null; synchronized String apprendre() { try { wait(); } catch (Exception e) {} return mot. Aapprendre; } synchronized void enseigner (String mot) { mot. Aapprendre = mot ; notify. All(); } }
Demi-synchronisation (wait et sleep) Exécution: nouveau mot pour perroquet ? (sinon non) coco bonjour Miam nouveau mot pour perroquet ? (sinon non) jaco Miam 1234 nouveau mot pour perroquet ? (sinon non) coco 1 nouveau mot pour perroquet ? (sinon non) jaco 3 nouveau mot pour perroquet ? (sinon non) coco 1 jaco 3 non Les perroquets répètent puis se mettent en attente d’un nouveau mot à apprendre dès qu’un nouveau mot est au tableau, les perroquets en attente sont notifiés (synchronisation avec wait et notify. All). Mais les perroquets qui ne sont pas en attente (qui ont été déjà notifiés) perdront certains mots si le professeur va trop vite (à cause du Thread. sleep(2000)). C’est une demi-synchronisation : Le professeur n’attend pas que les perroquets aient eu le temps de lire et répéter.
Simuler une station de bus q Des usagers arrivent à la station pour y attendre un bus ; un bus arrive, charge tous les usagers présents dans la station puis repart. q Si un usager arrive trop tard il loupe le bus. Station de bus
class Station { public synchronized void attendre. Bus () { try { wait(); } catch (Exception e) {} } public synchronized void charger. Usagers () { notify. All () ; } } class Usager extends Thread { private String nom ; private Station s ; private int heure. Arrivee ; public Usager (String nom, Station s, int heure. Arrivee) { this. nom = nom ; this. s = s ; this. heure. Arrivee = heure. Arrivee ; } public void run () { try { sleep (heure. Arrivee) ; } catch (Interrupted. Exception e) {} System. out. println (nom + " arrive a la station") ; s. attendre. Bus () ; System. out. println (nom + " est monte dans le bus") ; } } Utiliser par les usagers Utiliser par le bus class Bus extends Thread { private Station s ; private int heure. Arrivee ; public Bus (Station s, int heure. Arrivee) { this. s = s ; this. heure. Arrivee = heure. Arrivee ; } public void run () { try { sleep (heure. Arrivee) ; } catch (Interrupted. Exception e) {} System. out. println ("Bus arrive a la station") ; s. charger. Usagers () ; System. out. println ("Bus repart de la station") ; } }
Les 4 classes sont dans le fichier Bus. Simple. java class Bus. Simple { public static void main (String args[]) { Station Gare = new Station () ; Bus b = new Bus (Gare, 2000) ; Usager u [ ] = { new Usager ("A", Gare, 1500), new Usager ("B", Gare, 3000), new Usager ("C", Gare, 1000), new Usager ("D", Gare, 1500), new Usager ("E", Gare, 1000)} ; b. start () ; for (int i = 0 ; i < u. length ; i++) u[ i ]. start () ; } } Exécution : E arrive a la station C arrive a la station D arrive a la station A arrive a la station Bus repart de la station C est monte dans le bus E est monte dans le bus D est monte dans le bus A est monte dans le bus B arrive a la station
Simuler un match de foot entre 2 joueurs q 2 joueurs (2 threads) partage la même ressource qui est un ballon. q Au départ, les deux joueurs sont bloqués par un wait car ils demandent le ballon. q Le ballon est donné à l’un des deux joueurs (à l’aide de notify). q Un seul joueur peut récupérer le ballon, il possédera le ballon pendant certain moment (avec un sleep), il peut évidemment le déplacer, le lâcher par la suite et notifier le deuxième joueur qui va faire la même chose. 1 er thread 2ème thread
Problème du Producteur et du Consommateur public class Ecole. Du. Perroquet 18 { public static void main(String[] args) { Au. Tableau autableau = new Au. Tableau(); Perroquet 18 perroquet = new Perroquet 18("coco", autableau); perroquet. start(); //consommateur Maitre maitre = new Maitre(autableau); maitre. start(); //producteur }} class Maitre extends Thread { private Au. Tableau autableau; private String reponse = null; public Maitre (Au. Tableau a) { autableau = a ; } public void run() { for (int n=0; n<4; n++) { System. out. println("nouveau mot a enseigner au perroquet ? "); Thread. current. Thread(). yield(); reponse = Saisie. litexte(); autableau. enseigner(reponse); } }} class Perroquet 18 extends Thread { private String cri; private String nom; private Au. Tableau autableau; public Perroquet 18 (String n, Au. Tableau a) { super(n); nom = n; autableau = a ; cri = ""; } public void repeter() { System. out. println(nom + " "+ cri); } public void run() { while (true) { cri = autableau. apprendre(); for (int n=0; n<3; n++) { repeter(); try { Thread. sleep(2000); } catch(Interrupted. Exception e) { } }} class Au. Tableau { private String mot. Au. Tableau = null; synchronized String apprendre() { String mot. Aapprendre; while (mot. Au. Tableau == null) try { wait(); } catch (Exception e) {} mot. Aapprendre = mot. Au. Tableau ; mot. Au. Tableau = null; notify(); return mot. Aapprendre; } synchronized void enseigner (String mot. Nouveau) { while (mot. Au. Tableau != null) try { wait(); } catch (Exception e) {} mot. Au. Tableau = mot. Nouveau; notify(); } }
Problème du Producteur et du Consommateur q Le problème est simplifié, un seul professeur et un seul perroquet et chacun attend Exécution: l'autre : nouveau mot a enseigner au perroquet ? q Le perroquet attend pour un nouveau mot à apprendre. mille q Le professeur coco attend que le perroquet ait lu puis répété le mot. mille q Ce pattern classique est appelé : nouveau mot a. Producteur-Consommateur enseigner au perroquet ? q Il faut synchronisercoco le fonctionnement de chacun afin que : mille coco mille q Le consommateur attende quand il n’y a rien à consommer. q Le producteurbb attende quand le consommateur n'est pas prêt à consommer. coco bb nouveau mot a enseigner au perroquet ? coco bb non coco non nouveau mot a enseigner au perroquet ? coco non
Semaphore en JAVA q Un sémaphore encapsule un entier et deux opérations atomiques d’incrémentation et de décrémentation. q Avant 2005 (avant l’apparition de J 2 SE 5. 0). class Semaphore { int compteur; Semaphore (int init) { T 5 compteur=init; T 1 compteur = 4 } public synchronized void P(){ P() T 2 compteur--; T 3 if (compteur<0) try{wait(); } V() catch (Interrupted. Exception e){} T 4 } public synchronized void V(){ Section critique compteur++; if (compteur<=0) notify(); } Le compteur détermine le nombre max } de threads qui peuvent accéder à la section critique.
Semaphore en JAVA q Opération P (acquire()) : décrémente le compteur s'il est positif ; bloque s'il est négatif en attendant de pouvoir le décrémenter. q Opération V (release()) : incrémente le compteur. Fait un notify si le compteur est négatif ou nul. q On peut voir le sémaphore comme un ensemble de jetons, avec deux opérations : q Prendre un jeton, en attendant si nécessaire qu'il y en ait ; q acquire(). Probablement avec wait. q Déposer un jeton. q release(). Probablement avec notify. q Un sémaphore à 1 jeton est très similaire à un verrou. q La version 1. 5 de Java et son package java. util. concurrent (ainsi que ses souspackages) fournissent des outils de synchronisation de plus haut niveau.
java. util. concurrent. Semaphore import java. util. concurrent. Semaphore; class Process 2 extends Thread{ private int id; private Semaphore sem; public Process 2(int i, Semaphore s) { id = i; sem = s; } private void busy() { try{sleep((int)(Math. random()*1000)); } catch (Interrupted. Exception e){} } private void noncritical() { System. out. println("Thread " + id + " n'est pas dans la section critique"); busy(); } private void critical() { System. out. println("Thread " + id + " entre dans la section critique"); busy(); System. out. println("Thread " + id + " sort de la section critique"); } public void run() { noncritical(); try { sem. acquire(); } catch (Interrupted. Exception e){ } critical(); sem. release(); } public static void main(String[] args) { Semaphore sem = new Semaphore(1); Process 2[] p = new Process 2[4]; for (int i = 0; i < 4; i++) { p[i] = new Process 2(i, sem); p[i]. start(); } } }
Semaphore en JAVA q q q q Exécution : Semaphore sem = new Semaphore(1); Thread 0 n'est pas dans la section critique Thread 3 n'est pas dans la section critique Thread 2 n'est pas dans la section critique Thread 1 n'est pas dans la section critique Thread 3 entre dans la section critique Thread 3 sort de la section critique Thread 2 entre dans la section critique Thread 2 sort de la section critique Thread 1 entre dans la section critique Thread 1 sort de la section critique Thread 0 entre dans la section critique Thread 0 sort de la section critique
Semaphore en JAVA q q q q Exécution : Semaphore sem = new Semaphore(4); Thread 0 n'est pas dans la section critique Thread 1 n'est pas dans la section critique Thread 3 n'est pas dans la section critique Thread 2 n'est pas dans la section critique Thread 0 entre dans la section critique Thread 2 entre dans la section critique Thread 3 entre dans la section critique Thread 1 entre dans la section critique Thread 2 sort de la section critique Thread 3 sort de la section critique Thread 0 sort de la section critique Thread 1 sort de la section critique
Application des sémaphores q Gestion des entrées/sorties d’un parking q Un parking de capacité N places. q Gestion d’une salle d’attente. q Chez un médecin, coiffeur, assurance, … q M chaises disponibles.
Interblocage public class Interblocage { public static void main(String[] args) { final int[] tab 1 = { 1, 2, 3, 4 }; final int[] tab 2 = { 0, 1, 0, 1 }; final int[] tab. Add = new int[4]; final int[] tab. Sub = new int[4]; Thread tache. Add = new Thread() { public void run() { synchronized(tab 1) { System. out. println ("Thread tache. Add lock tab 1"); travail. Harassant(); synchronized(tab 2) { System. out. println ("Thread tache. Add lock tab 2"); for (int i=0; i<4 ; i++) tab. Add[i] = tab 1[i] + tab 2[i]; } }; Thread tache. Sub = new Thread() { public void run() { synchronized(tab 2) { System. out. println ("Thread tache. Sub lock tab 2"); travail. Harassant(); synchronized(tab 1) { System. out. println ("Thread tache. Sub lock tab 1"); for (int i=0; i<4 ; i++) tab. Add[i] = tab 1[i] - tab 2[i]; } }; tache. Add. start(); tache. Sub. start(); } static void travail. Harassant() { try { Thread. sleep((int)(Math. random()*50+25)); } catch (Interrupted. Exception e) {} } }
Interblocage q Supposons que tab 1 et tab 2 sont susceptibles d'être modifiés par d'autres threads qui effectueraient ces modifications en acquérant leur verrou. Pour éviter une modification au cours de leur calcul, les 2 threads cherchent à obtenir les verrous de tab 1 et tab 2 q Le problème d'interblocage est que : q La tache. Add a obtenu le lock de tab 1 et attend pour obtenir celui de tab 2, puis ils les libérera tous les 2. q La tache. Sub a obtenu le lock de tab 2 et attend pour obtenir celui de tab 1, puis ils les libérera tous les 2. q Ils sont donc bloqués, c'est un Deadlock. q Un interblocage de tâches parallèles qui ont acquis le verrou de certaines ressources et cherchent à en obtenir d'autres, le graphe des verrouillages est insastifaisable, comme les tâches ne peuvent revenir en arrière (cad libérer des ressources), la situation est définitivement bloquée. Exécution : Thread tache. Add lock tab 1 Thread tache. Sub lock tab 2 <ctrl-C>
- Slides: 82