Transcript

Badr Benmammar

[email protected]

Programmation concurrente et temps

réel en Java

class BavarderEtLancerLePerroquet4{ public static void main(String args[]) { Perroquet4 perroquet = new blabla(); Perroquet4("coco",5); perroquet.start(); System.out.println ("Thread bavard : " +

Thread.currentThread().getName());

for (int n=0; n<15; n++) { try { Thread.sleep(1000); } catch(InterruptedException e) { }

System.out.println("Thread perroquet isAlive : "

+ perroquet.isAlive()); }} private static void blabla() { System.out.println("blabla"); }}

class Perroquet4 extends Thread{ private String cri = null; private int fois = 0; public Perroquet4 (String s, int i) { cri = s; fois = i; } public void run() { System.out.println("Thread perroquet : " + Thread.currentThread().getName()); for (int n=0; n<fois; n++) { try { Thread.sleep(1000); } catch(InterruptedException e) { } System.out.println(cri); } }}

Cycle de vie d’un Thread

Exécution:Thread bavard : mainThread perroquet : Thread-0Thread perroquet isAlive : true blablacocococoThread perroquet isAlive : true blablacocoThread perroquet isAlive : true blablacocoThread perroquet isAlive : true blablacocoThread perroquet isAlive : false blablaThread perroquet isAlive : false blablaThread perroquet isAlive : false blablaThread perroquet isAlive : false blablaThread perroquet isAlive : false blablaThread perroquet isAlive : false blablaThread perroquet isAlive : false blablaThread perroquet isAlive : false blablaThread perroquet isAlive : false blablaThread perroquet isAlive : false blablaThread perroquet isAlive : false blabla

Cycle de vie d’un Thread

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 isAlive() détermine si un Thread est en train d'exécuter sa méthode run.

La méthode de classe currentThread() retourne un pointeur sur l’objet Thread qui appelle cette méthode.

La méthode getName() donne le nom du Thread.

Propriétés des différents Threads

class BavarderEtLancerLePerroquet5 {

public static void main(String args[]) {

Thread.currentThread().setName("bavard");

Perroquet5 perroquet = new Perroquet5("coco",15);

perroquet.start(); for (int n=0; n<5; n++) { try { Thread.sleep(1000); } catch(InterruptedException e) { } blabla(); }} private static void blabla() { System.out.println("blabla"); }}class Perroquet5 extends Thread { private String cri = null; private int fois = 0; public Perroquet5(String s, int i) { super("perroquet"); cri = s; fois = i; }

public void run(){ afficheThreads(); for (int n=0; n<fois; n++) { try { Thread.sleep(1000); } catch(InterruptedException e) { } System.out.println(cri); } afficheThreads(); } private void afficheThreads() { Thread[] tabThread = new Thread[Thread.activeCount()]; int nbrThread = Thread.enumerate(tabThread); for (int i = 0; i < nbrThread ; i++) System.out.println(i + "-ieme Thread : " + tabThread[i].getName()); }}

Propriétés des différents ThreadsExécution:0-ieme Thread : bavard1-ieme Thread : perroquetcocoblablacocoblablacocoblablacocoblablacocoblablacocococococococococococococococococococo0-ieme Thread : perroquet1-ieme Thread : DestroyJavaVM

La méthode setName (String nom) permet de nommer le Thread.

Le constructeur offre la possibilité de le nommer ainsi Thread (String nom).

La méthode de classe activeCount () 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 BavarderEtLancerLePerroquet6{

public static void main(String args[]) {

Perroquet6 perroquet = new Perroquet6("coco",10);

perroquet.start(); for (int n=0; n<5; n++) blabla(); try { perroquet.join(); } catch(InterruptedException e) {

System.out.println(e.getMessage());

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(InterruptedException e) { } }}

class Perroquet6 extends Thread { private String cri = null; private int fois = 0; public Perroquet6 (String s, int i) { super ("perroquet"); cri = s; fois = i; } public void repeter() { System.out.println(cri); try {Thread.sleep(1000); } catch(InterruptedException e) { } } public void run() { for (int n=0; n<fois; n++) repeter(); }}

Exécution: blablacocoblablacocoblablacocoblablacocococoblablacocococococococococofin du Thread perroquet !blablablablablablablablablabla

Synchronisation sur terminaison

Exécution: blablacocoblablacocoblablacocoblablacocococoblablacocococococococococofin du Thread perroquet !blablablablablablablablablabla

La méthode join() attend la terminaison du Thread spécifié.

Si le Thread est créé mais pas "starté", il est considéré comme terminé !

join() peut aussi avoir un paramètre donné qui est un timeout maximal.

Les 2 Threads ne sont plus indépendants puisque le "main" attend, à une certaine étape, la terminaison (fin d'exécution) de l'autre. C'est une forme de synchronisation.Exécution en multitâche

Le main attend la terminaison du Thread perroquet

Le main termine son exécution

perroquet.join();

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 (InterruptedException e) {} } System.out.println(texte.substring(j,j+1)); } System.out.println("ecrivain de " +texte+" a fini"); }}

public class Prog55 { public static void main (String argv[]) { Ecrivain ecrivainA, ecrivainB; ecrivainA = new Ecrivain ("ABC"); ecrivainB = new Ecrivain ("XYZ"); ecrivainA.start(); ecrivainB.start(); }}

Exemple de substring :String date = "15/08/2000";String jour = date.substring(0,2);// donne 15String mois = date.substring(3,5); // donne 08String annee = date.substring(6,10); // donne 2000Chaine du caractère 6 à 9 (premier

caractère d’indice 0).

Exemple de join() : synchroniser deux écrivains

Exécution:AXBYCABZXCAYZXBCAYBCAZXBCAYBCAZXBCABYZXCAYBCAZXBCecrivain de ABC a finiYZXYZXYZXYZecrivain de XYZ a fini

public class Prog55 { public static void main (String argv[]) { Ecrivain ecrivainA, ecrivainB; ecrivainA = new Ecrivain ("ABC"); ecrivainB = new Ecrivain ("XYZ"); ecrivainA.start();try { ecrivainA.join(); } catch(InterruptedException e) { System.out.println(e.getMessage()); System.exit(1); }ecrivainB.start(); }}

Exécution :ABCABCABCABCABCABCABCABCABCABCecrivain de ABC a finiXYZXYZXYZXYZXYZXYZXYZXYZXYZXYZecrivain de XYZ a fini

Deux Threads sans sleep

class BavarderEtLancerLePerroquet7 { public static void main(String args[])

{ Perroquet7 perroquet = new Perroquet7("coco",10); perroquet.start(); for (int n=0; n<10; n++) blabla(); } private static void blabla() { System.out.println("blabla"); }}class Perroquet7 extends Thread { private String cri = null; private int fois = 0; public Perroquet7(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:blablablablablablablablablablablablablablablablablablablablacocococococococococococococococococococo

Le temps d’exécution est trop court pourvisualiser la répartitiondu CPU entre les 2 Threads.

public class CourseInfernale1 { 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 coupsDePedale = 0; while (coupsDePedale < 5000000) { coupsDePedale++; if ((coupsDePedale % 500000) == 0) { System.out.println("Coureur " + nom + " a donne " + coupsDePedale + " coups de pedale."); } } }}

Répartition de temps entre deux Threads

Répartition de temps entre deux Threads

Exécution:Coureur A a donne 500000 coups de pedale.Coureur B 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 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 niveaude priorité.

Priorité entre Threads La méthode setPriority fixe le niveau de priorité entre les différents

Threads. La valeur doit être comprise entre une valeur minimale, MIN_PRIORITY,

et maximale, MAX_PRIORITY.

public class CourseInfernale2 { public static void main(String[] args) { Coureur A = new Coureur("A"); Coureur B = new Coureur("B"); A.setPriority(Thread.MAX_PRIORITY); B.setPriority(Thread.MIN_PRIORITY); System.out.println("Thread Coureur " + A.nom + " a la priorite = " + A.getPriority()); System.out.println("Thread Coureur " + B.nom + " a la priorite = " + B.getPriority()); A.start(); B.start(); }}

class Coureur extends Thread { String nom; public Coureur(String nom) { super(nom); this.nom = nom; } public void run() { long coupsDePedale = 0; while (coupsDePedale < 5000000) { coupsDePedale++; if ((coupsDePedale % 500000) == 0) { System.out.println("Coureur " + nom + " a donne " + coupsDePedale + " coups de pedale."); } } }}

Priorité entre Threads Exécution:Thread Coureur A a la priorite = 10Thread Coureur B a la priorite = 1Coureur 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() 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 BavarderEtLancerLePerroquet8 { public static void main(String args[]) { Perroquet8 perroquet = new Perroquet8("coco",10); perroquet.start(); for (int n=0; n<10; n++) { blabla(); Thread.currentThread().yield(); } } private static void blabla() { System.out.println("blabla"); }}

class Perroquet8 extends Thread { private String cri = null; private int fois = 0; public Perroquet8(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 Threadclass

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

{ Perroquet9 perroquet = new

Perroquet9("coco"); perroquet.start(); String reponse="o"; do {System.out.println("voulez-vous

que le perroquet continue ? (o/n)"); Thread.currentThread().yield(); reponse = Saisie.litexte(); } while (reponse.equals("o")); }}Exécution:cocococococococococo…

class Perroquet9 extends Thread { private String cri = null; public Perroquet9(String s) { cri = s; } public void repeter() { System.out.println(cri); try { Thread.sleep((int)Math.random()*1000); } catch(InterruptedException e) {} } public void run() { while (true) { repeter(); yield(); } }}

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 LancerEtArreterLePerroquet10{

public static void main(String args[]) {

Perroquet10 perroquet = new Perroquet10("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 Perroquet10 extends Thread{ private volatile boolean continuer = true; private String cri = null; public Perroquet10(String s) { continuer = true; cri = s; } public void repeter() { System.out.println(cri); try { Thread.sleep((int)Math.random()*2000); } catch(InterruptedException e) { } } public void stopper() { continuer = false;} public void run() { while (continuer) { repeter(); } }}

Stopper proprement

Exécution :cocococococococococococococococococococococococoncocococococococococococo

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.TimerTask;import java.util.Timer;class DeclancherLePerroquet11{ public static void main(String

args[]) { Perroquet11 perroquet = new

Perroquet11("coco", 3); Timer timer = new Timer(); timer.schedule(perroquet,

4000); String reponse="oui"; do { System.out.println("blabla"); System.out.println("blabla"); System.out.println("voulez-

vous encore bavarder ? (o/n)"); reponse = Saisie.litexte(); } while (reponse.equals("o")); timer.cancel(); }}

class Perroquet11 extends TimerTask { private String cri = null; private int fois = 0; public Perroquet11(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(); } }}

TimerTask

public abstract class TimerTask

extends Object

implements Runnable

TimerTask est une classe abstraite qui implémente Runnable, donc

une méthode run(), il faut donc hériter de la classe TimerTask et

redéfinir la méthode run() qui code la tâche à effectuer.

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:blablablablavoulez-vous encore bavarder ?

(o/n)oblablablablavoulez-vous encore bavarder ?

(o/n)cocococococooblablablablavoulez-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 TimerTask 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.TimerTask;import java.util.Timer;class DeclancherLePerroquet11{ public static void main(String

args[]) { Perroquet11 perroquet = new

Perroquet11("coco", 3); Timer timer = new Timer(); timer.schedule(perroquet,

3000,2000); String reponse="oui"; do { System.out.println("blabla"); System.out.println("blabla"); System.out.println("voulez-

vous encore bavarder ? (o/n)"); reponse = Saisie.litexte(); } while (reponse.equals("o")); timer.cancel(); }}

class Perroquet11 extends TimerTask { private String cri = null; private int fois = 0; public Perroquet11(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(); } }}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:blablablablavoulez-vous que le perroquet continue ? (o/n)cocococococococococococoncocococococo

Partager une ressource: imprimeurpublic class Imprimeur1 { private String texte; public Imprimeur1() { 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 (InterruptedException e) {}; } System.out.println(texte.substring(texte.length()-

1,texte.length())); }}

public class Ecrivain2 extends Thread { private String texte; private Imprimeur1 imprim; public Ecrivain2(String t, Imprimeur1 i) { imprim=i; texte=t; } public void run() { for (int i=0; i<10; i++) { imprim.imprimer(texte); try { sleep((long)(Math.random() * 100)); } catch (InterruptedException e) {} } System.out.println("ecrivain de " +texte+" a fini"); }}

public class Prog56 { public static void main (String argv[]) { Ecrivain2 ecrivainA, ecrivainB; Imprimeur1 imprim= new Imprimeur1(); ecrivainA = new Ecrivain2("ABC", imprim); ecrivainB = new Ecrivain2("XYZ", imprim); ecrivainA.start(); ecrivainB.start(); }}

Exécution:AXYYZZAXYYZZAXYYZACXYYZZXABBCCAXYYZZAXYYZZXABBCCXABBCCXABBCecrivain de XYZ a finiCecrivain de ABC a fini

Partager une ressource: imprimeur

Les écrivains passent par un imprimeur pour écrire.

Les 2 écrivains s'adressent maintenant à un imprimeur commun.

Non seulement, les écrivains écrivent en "interleaving" (entrelacement),mais ils "écrasent" la variable texte de l'imprimeur.

L'ensemble est encore illisible.

Ressource en exclusion mutuellepublic class Imprimeur1 { private String texte; public Imprimeur1() { 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 (InterruptedException e) {}; } System.out.println(texte.substring(texte.length()-

1,texte.length())); }}

Exécution :ABCXYZABCXYZABCXYZABCXYZABCXYZABCXYZABCXYZABCXYZABCXYZABCXecrivain de ABC a finiYZecrivain de XYZ a fini

Ressource en exclusion mutuelle

synchronized définit un verrou/ une section en exclusion mutuelle sur la méthode imprimer : Un seul Thread au plus peut exécuter la méthode à la fois.

Synchronisation des threads

Plan Variables partagées

Problème de l’exclusion mutuelle

Bloc synchronisé

Méthode d’instance synchronisée

Problème de coopération des threads

wait, notifyAll et notify

Demi-synchronisation (wait et sleep)

Problème du Producteur et du Consommateur

Producteurs-Consommateurs

Sémaphore

Interblocage

Variables partagéesclass PerroquetsMatheux20 { private int compteur; public static void main(String args[]) { new PerroquetsMatheux20(); } public PerroquetsMatheux20 () { compteur = 1; Perroquet20 perroquetA = new Perroquet20("coco", 10); Perroquet20 perroquetB = new Perroquet20("bonjour", 10); perroquetA.start(); perroquetB.start(); try { perroquetA.join(); perroquetB.join(); } catch(InterruptedException e) { } System.out.println("compteur =

"+compteur); }

class Perroquet20 extends Thread { private String cri = null; private int fois = 0; public Perroquet20 (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(InterruptedException e) { } } public void run(){ for (int n=0; n<fois; n++) repeter(); } }}

Exécution:coco 1bonjour 2bonjour 3coco 4bonjour 5coco 6bonjour 7bonjour 8bonjour 9coco 10bonjour 11coco 12bonjour 13coco 14coco 15bonjour 16coco 17bonjour 18coco 19coco 20compteur = 21

Variables partagées

La classe Perroquet20 se situe à l’intérieur de la classe PerroquetsMatheux20, 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 Perroquet20 donc des 2 objets threads perroquetA et perroquetB, par contre, les variables d'instance cri et fois de Perroquet20 existent en autant d'exemplaires que d'instances de Perroquet20.

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 PerroquetsMatheux21{ private int compteur; public static void main(String args[]) { new PerroquetsMatheux21(); } public PerroquetsMatheux21() { compteur = 0; Perroquet21 perroquetA = new Perroquet21("coco", 10); Perroquet21 perroquetB = new Perroquet21("bonjour", 10);//

perroquetA.setPriority(perroquetB.getPriority()%2);

perroquetA.start(); perroquetB.start(); try { perroquetA.join(); perroquetB.join(); } catch(InterruptedException e) { } System.out.println("compteur =

"+compteur); }

class Perroquet21 extends Thread { private String cri = null; private int fois = 0; public Perroquet21(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(InterruptedException e) { } compteur = valeur; try {Thread.sleep((int)(Math.random()*100));} catch(InterruptedException 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 1bonjour 1coco 2coco 3bonjour 3coco 4bonjour 4coco 5bonjour 5coco 6bonjour 7coco 7bonjour 8coco 8bonjour 9coco 9coco 10bonjour 10bonjour 11bonjour 12compteur = 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 PerroquetsMatheux22{ private Compteur compteur; public static void main(String args[]) { new PerroquetsMatheux22(); } public PerroquetsMatheux22() { compteur = new Compteur(); Perroquet22 perroquetA = new Perroquet22("coco", 10); Perroquet22 perroquetB = new Perroquet22("bonjour", 10);perroquetA.setPriority(perroquetB.getPriority()

%2); perroquetA.start(); perroquetB.start(); try { perroquetA.join(); perroquetB.join(); } catch(InterruptedException e) { } System.out.println("compteur =

"+compteur.getValeur()); } class Perroquet22 extends Thread { private String cri = null; private int fois = 0; public Perroquet22(String s, int i) { cri = s; fois = i; }

public void repeter() { synchronized (compteur) { int valeur = compteur.getValeur() + 1; String repete = cri + " " + valeur; System.out.println(repete); try { Thread.sleep((int)(Math.random()*100)); } catch(InterruptedException e) { } compteur.setValeur(valeur); } try { Thread.sleep((int)(Math.random()*100)); } catch(InterruptedException e) { } } public void run(){ for (int n=0; n<fois; n++) repeter(); } } class Compteur { private int valeur = 0; public int getValeur() { return valeur; } public void setValeur(int v) { valeur = v; } }}

Définir une section critique : Bloc synchronisé

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 : Lorsque le thread perroquetA exécute ce bloc

synchronisé, et que le thread perroquetB souhaite commencer l'exécution de ce même bloc, alors le thread perroquetB doit attendre. Quand le thread perroquetA aura finit, le thread perroquetB pourra reprendre.

Seul un thread à la fois peut exécuter un bloc synchronisé. On dit que le bloc est en exclusion mutuelle ou encore

que c'est une section critique. Les autres threads, s'ils désirent exécuter cette section,

doivent attendre que le thread en section critique la termine.

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.

L'appel à sleep() ne provoque pas de sortie de la section critique.

Exécution:bonjour 1coco 2bonjour 3coco 4bonjour 5coco 6coco 7bonjour 8coco 9bonjour 10bonjour 11coco 12coco 13bonjour 14coco 15bonjour 16bonjour 17coco 18coco 19bonjour 20compteur = 20

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éeclass PerroquetsMatheux23 { private Compteur compteur; public static void main(String args[]) { new PerroquetsMatheux23(); } public PerroquetsMatheux23() { compteur = new Compteur(); Perroquet23 perroquetA = new Perroquet23("coco", 10); Perroquet23 perroquetB = new Perroquet23("bonjour", 10);perroquetA.setPriority(perroquetB.getPri

ority()%2); perroquetA.start(); perroquetB.start(); try { perroquetA.join(); perroquetB.join(); } catch(InterruptedException e) { } System.out.println("compteur =

"+compteur.valeur); } class Perroquet23 extends Thread { private String cri = null; private int fois = 0; public Perroquet23(String s, int i) { cri = s; fois = i; }

public void repeter() { int valeur = compteur.plus1(); String repete = cri + " " + valeur; System.out.println(repete); try { Thread.sleep((int)(Math.random()*100)); } catch(InterruptedException e) { } } public void run(){ for (int n=0; n<fois; n++) repeter(); } } class Compteur { private int valeur = 0; public synchronized int plus1() { return ++valeur; } }}

Méthode d’instance synchroniséeExécution

:coco 1bonjour 2bonjour 3coco 4coco 5bonjour 6bonjour 7coco 8bonjour 9coco 10bonjour 11coco 12bonjour 13bonjour 14coco 15bonjour 16coco 17bonjour 18coco 19coco 20compteur =

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}

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.

est équivalent à : méthode(paramètres) { synchronized(this) { bloc d'instructions }}

Rappel

Multitâche Un système d’exploitation est dit multitâche ou à temps partagé lorsque

plusieurs «tâches» (processus) peuvent être exécutées simultanément. 2 types de système d’exploitation multitâche :

Multitâche coopératif (non-préemptif) : 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 : 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.

Multitâche préemptif : 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. Ne pas attendre des heures qu’un programme planté cède la

priorité.

Processus vs Thread

La plupart des systèmes d’exploitation offrent la distinction

entre :

Processus lourd :

Un programme (un ensemble d’instructions) à

exécuter.

Sont complètement isolés les uns des autres.

Processus léger : Thread

Portion de code capable de s’exécuter en parallèle à

d’autres traitements.

Ils partagent code, données et ressources.

Mais peuvent disposer de leurs propres données.

Création de thread

2 manières pour créer un Thread :

Une classe qui dérive de java.lang.Thread.

java.lang.Thread implémente Runnable.

Il faut redéfinir la méthode run().

Une classe qui implémente l’interface Runnable

Il faut implémenter la méthode run()

Méthode 1 : Sous-classer Thread

class Thread1 extends Thread {

Thread1() {...} // Le constructeur

...

public void run() {

... // Ici ce que fait le thread

}

}

...

Thread1 p1 = new Thread1(); // Création du thread p1

p1.start(); // Démarre le thread et exécute p1.run()

Méthode 2 : une classe qui implémente Runnable

class Thread2 implements Runnable {

Thread2() { ...} // Constructeur

...

public void run() {

... // Ici ce que fait le thread

}

}

...

Thread2 p = new Thread2();

Thread p2 = new Thread(p);

...

p2.start(); // Démarre le thread et exécute p.run()

Quelle solution choisir ?

Méthode 1 : sous-classer Thread

Lorsqu’on désire paralléliser une classe qui n’hérite pas déjà

d’une autre classe (classe autonome).

Attention : héritage simple.

Méthode 2 : implémenter Runnable

Lorsqu’une super-classe est imposée.

Cas des applets

public class MyThreadApplet

extends Applet implements Runnable {}

Applet et thread 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 CounterThread 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 (InterruptedException e) {} } } public void paint(Graphics g) { g.drawString(Integer.toString(Count),10,10); System.out.println("Count= "+Count); }}

ThreadGroup

ThreadGroup : dans java.lang.

Plusieurs Threads peuvent s’exécuter en même temps, il serait

utile de pouvoir les manipuler comme une seule entité.

Pour les suspendre,

Pour les arrêter, ...

Java offre cette possibilité via l’utilisation des groupes de

threads : java.lang.ThreadGroup.

On groupe un ensemble nommé de threads.

Ils sont contrôlés comme une seule unité.

La JVM crée au minimum un groupe de threads nommé main.

Par défaut, un thread appartient au même groupe que celui

qui l’a crée (son père).

getThreadGroup() : pour connaitre son groupe.

Création d’un groupe de threads

Pour créer un groupe de threads :ThreadGroup groupe1 = new ThreadGroup("GP1");Thread p1 = new Thread(groupe1, "P1");Thread p2 = new Thread(groupe1, "P2");Thread p3 = new Thread(groupe1, "P3");

Le contrôle des ThreadGroup passe par l’utilisation des méthodes

standards qui sont partagées avec Thread :

Exemple : interrupt(), destroy(),

Par exemple : appliquer la méthode interrupt() à un

ThreadGroup revient à invoquer pour chaque Thread du

groupe cette même méthode.

Création d’une arborescence de threads

ThreadGroup groupe1 = new ThreadGroup("GP1");Thread p1 = new Thread(groupe1, "P1");Thread p2 = new Thread(groupe1, "P2");Thread p3 = new Thread(groupe1, "P3");ThreadGroup groupe11 = new ThreadGroup(groupe1, "GP11");Thread p4 = new Thread(groupe11, "P4");Thread p5 = new Thread(groupe11, "P5");

Threads démons

2 types de threads : Les threads utilisateur :

L’activité de ce type de thread est limitée dans le temps, c’est à dire que son scénario est un algorithme qui se termine au bout d’un temps fini.

La JVM fonctionne tant qu’il reste des threads utilisateurs en exécution.

Les démons : Threads qui s’exécutent en tâche de fond tant que le programme

tourne. Difficile de trouver une condition d’arrêt pour ce type de thread. Un démon “meurt” lorsque l’application se termine. La JVM s’arrête s’il ne reste plus que des démons. Les démons sont là seulement pour rendre service aux threads

utilisateur. Exemple :

Ramasse-miettes (DestroyJavaVM). Un thread horloge qui tourne en tâche de fond.

Threads démons

La méthode void setDaemon (boolean) de la classe Thread permet d’indiquer que le thread sera un démon (thread utilisateur par défaut).

Elle doit être appelée avant le démarrage du thread par l’appel de start().

Exemple :class Horloge extends Thread { public Horloge () { setDaemon (true) ; } public void run () { while (true) { try { Thread.sleep (300) ; } catch (InterruptedException e) {} System.out.println ("tip") ; } }}On peut lancer l’horloge avec la simulation de course.

Synchronisation des threads

Premier type de synchronisation :

Synchronisation compétitive : lorsque plusieurs threads utilisent la même ressource (exemple : imprimeur, l’accès concurrents à un compte en banque).

Définir un verrou / une section en exclusion mutuelle.

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.

synchronized (objet) { instr } moniteur associé à objet.

synchronized void Methode(){....} moniteur associé à la méthode.

L’objet est verrouillé pendant que le bloc synchronisé est exécuté.

T1 est entrain

d’exécuter

le bloc

synchronisé

T2

T3

T4

synchronized (objet)

Queue d’attenteEn Java, chaque objet

possède une

queue d’attente

Moniteur associe à un objet

1 seul moniteur

Moniteurs associent à 2 objets

2 moniteursLe thread qui exécute synchronized d’un objet devient propriétaire du moniteur de cet objet

Synchronisation des threads Deuxième type de synchronisation :

Synchronisation coopérative : lorsqu’un thread attend la fin de l’exécution d’un autre avant de poursuivre son exécution.

Priorités entre threads : méthode setPriority(entier). Les priorités des threads se situent dans un intervalle de 1 à

10. Trois constantes représentent les valeurs limites et médianes. Plus la valeur de la priorité est élevée, plus grande sera la

priorité du thread pour l'accès au processeur. MIN_PRIORITY : priorité minimum (1) NORM_PRIORITY : priorité normale (5) MAX_PRIORITY : priorité maximum (10)

La priorité normale est affectée par défaut à un nouveau thread.

Coureur A = new Coureur("A");Coureur B = new Coureur("B"); A.setPriority(Thread.MAX_PRIORITY);B.setPriority(Thread.MIN_PRIORITY);

Synchronisation des threads

Méthode join() : synchroniser deux écrivains

ecrivainA = new Ecrivain ("ABC");

ecrivainB = new Ecrivain ("XYZ");

ecrivainA.start();

try { ecrivainA.join(); }

catch(InterruptedException e) {}

ecrivainB.start();

Autre type de coopération avec : wait, notify, notifyAll.

Sémaphores.

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

Parler

Maison

2 Threads : 2 élèves

Problème de coopération des threadspublic class EcoleDesPerroquets14 {

static String mot = null;

public static void main(String[] args) {

Perroquet14 perroquet1

= new Perroquet14("coco");

perroquet1.start();

Perroquet14 perroquet2

= new Perroquet14("jaco");

perroquet2.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 Perroquet14 extends Thread { private String nom; public Perroquet14(String n) { super(n); nom = n; } public void repeter() { System.out.println(nom + " "+ EcoleDesPerroquets14.mot); } public void run() { while (true) { while (EcoleDesPerroquets14.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(InterruptedException e) { } } }}

Problème de coopération des threadsExécution:nouveau mot pour perroquet ? (sinon non)blanouveau mot pour perroquet ? (sinon non)coco blacoco blacoco blajaco blajaco blajaco blablunouveau mot pour perroquet ? (sinon non)coco blucoco blucoco blublococo blucoco blucoco blucoco blucoco blucoco blunouveau mot pour perroquet ? (sinon non)jaco blojaco blojaco blojaco blojaco 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.

Coopération avec : wait et notifyAllpublic class EcoleDesPerroquets15 { public static void main(String[]

args) { AuTableau autableau = new

AuTableau();

Perroquet15 perroquet1

= new Perroquet15("coco", autableau);

perroquet1.start(); Perroquet15 perroquet2

= new Perroquet15("jaco", autableau);

perroquet2.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 Perroquet15 extends Thread { private String cri;

private String nom;

private AuTableau autableau;

public Perroquet15(String n, AuTableau 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(); } }}class AuTableau { private String motAapprendre = null; synchronized String apprendre() { try { wait(); } catch (Exception e) {} return motAapprendre; } synchronized void enseigner (String mot) { motAapprendre = mot ; notifyAll(); }}

Utiliser par les élèves

Utiliser par le professeur

Coopération avec : wait et notifyAllExécution:nouveau mot pour perroquet ? (sinon non)miamnouveau mot pour perroquet ? (sinon non)coco miamcoco miamcoco miamjaco miamjaco miamjaco miamencorecoco encorecoco encorecoco encorejaco encorejaco encorejaco encorenouveau mot pour perroquet ? (sinon non)non

Coopération avec : wait et notifyAll

Les trois méthodes (wait, notify et notifyAll) sont définies dans la classe java.lang.Object et sont donc héritées par toute classe.

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 notifyAll.

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.

Une autre version de wait prend en argument un entier de type long qui définit la durée d’attente maximale (en millisecondes). Si ce temps est dépassé, le thread est réveillé.

Différence entre sleep et wait : sleep bloque l’exécution mais le thread garde le verrou (termine son

exécution). 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 notifyAll

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 (notifyAll) aux perroquets

en attente qu’ils peuvent le lire et le répéter.

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();

...}

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 notifyAll

La méthode notifyAll() 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.notifyAll();

...}

Indique au moniteur de l’objet que tous les threads en attente (wait)

sur l'objet doivent être réveillés.

Coopération avec : wait et notifyAll

notifypublic class EcoleDesPerroquets16 {

public static void main(String[] args) {

AuTableau autableau = new AuTableau();

Perroquet16 perroquet1

= new Perroquet16("coco", autableau);

perroquet1.start(); Perroquet16 perroquet2

= new Perroquet15("jaco", autableau);

perroquet2.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 Perroquet16 extends Thread {

private String cri;

private String nom;

private AuTableau autableau;

public Perroquet15(String n, AuTableau 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(); } }}class AuTableau { private String motAapprendre = null; synchronized String apprendre() { try {wait(); } catch (Exception e) {} return motAapprendre; } synchronized void enseigner (String mot) { motAapprendre = mot ; notify(); }}

notifyExécution:coco bonjourcoco bonjourcoco bonjourmiamcoco miamcoco miamcoco miamnouveau mot pour perroquet ?

(sinon non)encorejaco encorejaco encorejaco encorenouveau mot pour perroquet ?

(sinon non)bizarrecoco bizarrecoco bizarrecoco bizarrenouveau mot pour perroquet ?

(sinon non)vraimentjaco vraimentjaco vraimentjaco vraimentnouveau mot pour perroquet ?

(sinon non)non

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.

Demi-synchronisation (wait et sleep)public class EcoleDesPerroquets17 { public static void main(String[] args)

{ AuTableau autableau = new

AuTableau();

Perroquet15 perroquet1

= new Perroquet15("coco", autableau);

perroquet1.start(); Perroquet15 perroquet2

= new Perroquet15("jaco", autableau);

perroquet2.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 Perroquet17 extends Thread {

private String cri;

private String nom;

private AuTableau autableau;

public Perroquet15(String n, AuTableau 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(InterruptedException e) { } } } }}class AuTableau { private String motAapprendre = null; synchronized String apprendre() { try { wait();} catch (Exception e) {} return motAapprendre;} synchronized void enseigner (String mot) { motAapprendre = mot ; notifyAll(); }}

Demi-synchronisation (wait et sleep)Exécution:nouveau mot pour perroquet ? (sinon non)coco bonjourcoco bonjourcoco bonjourMiamnouveau mot pour perroquet ? (sinon non)jaco Miamjaco Miamjaco Miam1 2 3 4nouveau mot pour perroquet ? (sinon non)coco 1nouveau mot pour perroquet ? (sinon non)nouveau mot pour perroquet ? (sinon non)jaco 3nouveau mot pour perroquet ? (sinon non)coco 1jaco 3coco 1jaco 3non

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 notifyAll).

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

Des usagers arrivent à la station pour y attendre un bus ; un bus arrive, charge tous les usagers présents dans la station puis repart. Si un usager arrive trop tard il loupe le bus.

Station de bus

class Station {

public synchronized void attendreBus () {

try { wait(); } catch (Exception e) {}

}

public synchronized void chargerUsagers ()

{ notifyAll () ; }

}

class Usager extends Thread {

private String nom ;

private Station s ;

private int heureArrivee ;

public Usager (String nom, Station s, int heureArrivee) {

this.nom = nom ;

this.s = s ;

this.heureArrivee = heureArrivee ;

}

public void run () {

try { sleep (heureArrivee) ; }

catch (InterruptedException e) {}

System.out.println (nom + " arrive a la station") ;

s.attendreBus () ;

System.out.println (nom + " est monte dans le bus") ;

}

}

class Bus extends Thread {

private Station s ;

private int heureArrivee ;

public Bus (Station s, int heureArrivee) {

this.s = s ;

this.heureArrivee = heureArrivee ;

}

public void run () {

try {

sleep (heureArrivee) ;

}

catch (InterruptedException e) {}

System.out.println ("Bus arrive a la station") ;

s.chargerUsagers () ;

System.out.println ("Bus repart de la station") ;

}

}

Utiliser par les usagers

Utiliser par le bus

Les 4 classes sont dans le fichier BusSimple.java

class BusSimple { 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 stationC arrive a la stationD arrive a la stationA arrive a la stationBus arrive a la

stationBus repart de la

stationC est monte dans le

busE est monte dans le

busD est monte dans le

busA est monte dans le

busB arrive a la station

Simuler un match de foot entre 2 joueurs

2 joueurs (2 threads) partage la même ressource qui est un ballon.

Au départ, les deux joueurs sont bloqués par un wait car ils demandent le ballon.

Le ballon est donné à l’un des deux joueurs (à l’aide de notify).

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.

1er thread 2ème thread

Problème du Producteur et du Consommateurpublic class EcoleDuPerroquet18

{ public static void main(String[]

args) { AuTableau autableau = new

AuTableau(); Perroquet18 perroquet = new

Perroquet18("coco", autableau); perroquet.start(); //consommateur Maitre maitre = new

Maitre(autableau); maitre.start(); //producteur }}class Maitre extends Thread { private AuTableau autableau; private String reponse = null; public Maitre (AuTableau a) { autableau = a ; } public void run() { for (int n=0; n<4; n++) { System.out.println("nouveau mot

a enseigner au perroquet ?"); Thread.currentThread().yield(); reponse = Saisie.litexte(); autableau.enseigner(reponse); } }}

class Perroquet18 extends Thread { private String cri; private String nom; private AuTableau autableau; public Perroquet18(String n, AuTableau 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(InterruptedException e) { } } }}}

class AuTableau { private String motAuTableau = null; synchronized String apprendre() { String motAapprendre; while (motAuTableau == null) try { wait(); } catch (Exception e) {} motAapprendre = motAuTableau ; motAuTableau = null; notify(); return motAapprendre; } synchronized void enseigner(String motNouveau) { while (motAuTableau != null) try { wait(); } catch (Exception e) {} motAuTableau = motNouveau; notify(); }}

Problème du Producteur et du Consommateur

Le problème est simplifié, un seul professeur et un seul perroquet et chacun attend l'autre : Le perroquet attend pour un nouveau mot à apprendre. Le professeur attend que le perroquet ait lu puis répété le

mot. Ce pattern classique est appelé Producteur-Consommateur : Il faut synchroniser le fonctionnement de chacun afin que :

Le consommateur attende quand il n’y a rien à consommer. while (motAuTableau == null)

Le producteur attende quand le consommateur n'est pas prêt à consommer.

while (motAuTableau != null)Producteur

Consommateur

wait()

wait()

notify ()

notify ()

En attente du produit (rien à consommer)

Dépose le produit

En attente (Le consommateur n’est pas prêt à consommer)

12

3

Réveiller le producteur

5 Consommer le produit

4

Problème du Producteur et du Consommateur

Exécution:nouveau mot a enseigner au perroquet ?millecoco millenouveau mot a enseigner au perroquet ?coco millecoco millebbcoco bbnouveau mot a enseigner au perroquet ?coco bbcoco bbnoncoco nonnouveau mot a enseigner au perroquet ?coco noncoco non

Producteurs-Consommateurs Des threads "producteur" qui produisent des données et les

mettent dans une file de messages. Des threads "consommateur" qui prennent les données de la file. Les producteurs et les consommateurs ne doivent pas accéder à

la file en même temps (section critique).

File de messages

Producteurs

Consommateurs

P1

P2

P3

P4

P5

C1

C2

C3

Producteurs-Consommateurs

Synchronisation par rapport à la file de message.

Les producteurs :

Quand la file est pleine : bloquer.

Quand il existe des places libres : débloquer.

Les consommateurs :

Quand la file est vide : bloquer.

Quand il existe des données dans la file : débloquer.

Producteurs-Consommateurs Un moniteur d’accès propose des méthodes pour accéder à la file

de manière contrôlée.import java.util.ArrayList;public class Moniteur {

private ArrayList<Object> file;// file de messages.public Moniteur(int capacite) { … }file=new ArrayList<Object>(capacite); // créer un moniteur pour une file d'attente de capacité spécifiéepublic synchronized void deposer (Object o) { … }// déposer une donnée. bloquer si la file est pleine.public synchronized Object prendre() { … }// prendre une donnée. bloquer si la file est vide}

// producteurwhile (…) {moniteur.deposer(o);}

// consommateurwhile (…) {System.out.println(moniteur.prendre());}

Utiliser par les producteurs

Utiliser par les consommateurs

Semaphore en JAVA

Un sémaphore encapsule un entier et deux opérations atomiques d’incrémentation et de décrémentation.

Avant 2005 (avant l’apparition de J2SE 5.0).class Semaphore {int compteur;Semaphore (int init){compteur=init;}public synchronized void P(){compteur--;if (compteur<0) try{wait();} catch (InterruptedException e){}}public synchronized void V(){compteur++;if (compteur<=0) notify();}}

P()

V()

compteur = 4 T1

T2T3

T4

T5

Section critique

Le compteur détermine le nombre max de threads qui peuvent accéder à la section

critique.

Semaphore en JAVA

Opération P (acquire()) : décrémente le compteur ; bloque s'il est négatif en attendant de pouvoir le décrémenter.

Opération V (release()) : incrémente le compteur. Fait un notify si le compteur est négatif ou nul.

On peut voir le sémaphore comme un ensemble de jetons, avec deux opérations : Prendre un jeton, en attendant si nécessaire qu'il y en ait ;

acquire(). Probablement avec wait. Déposer un jeton.

release(). Probablement avec notify. Un sémaphore à 1 jeton est très similaire à un verrou.

La version 1.5 de Java et son package java.util.concurrent (ainsi que ses sous-packages) fournissent des outils de synchronisation de plus haut niveau.

java.util.concurrent.Semaphoreimport java.util.concurrent.Semaphore;class Process2 extends Thread{ private int id;

private Semaphore sem;

public Process2(int i, Semaphore s) {

id = i; sem = s; } private void busy() {

try{sleep((int)(Math.random()*1000));} catch (InterruptedException 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 (InterruptedException e){

} critical(); sem.release(); } public static void main(String[] args)

{ Semaphore sem = new

Semaphore(1); Process2[] p = new Process2[4]; for (int i = 0; i < 4; i++) { p[i] = new Process2(i, sem); p[i].start(); } }}

Semaphore en JAVA

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

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

Gestion des entrées/sorties d’un parking

Un parking de capacité N places.

Gestion d’une salle d’attente.

Chez un médecin, coiffeur, assurance, …

M chaises disponibles.

Exercice : Coiffeur dormeur (producteur/consommateur avec

sémaphore) Un coiffeur possède un salon avec un siège de coiffeur et une

salle d’attente comportant un nombre fixe N de fauteuils. S’il n’y a pas de client, le coiffeur se repose sur son siège de

coiffeur. Quand un client arrive :

Si la salle d’attente est pleine, le client repasse plus tard. S’il trouve le coiffeur endormi, il le réveille, s’assoit sur le siège

de coiffeur et attend la fin de sa coupe de cheveux. Si le coiffeur est occupé lorsque le client arrive, le client s’assoit

et s’endort sur une des N chaises de la salle d’attente, il doit attendre que le siège de coiffeur soit libre.

Lorsque le coiffeur a terminé une coupe de cheveux, il fait sortir son client courant et va réveiller un des clients de la salle d’attente.

Si la salle d’attente est vide, le coiffeur se rendort sur son siège jusqu’`a ce qu’un nouveau client arrive.

Le but de cet exercice est d’associer un thread au coiffeur ainsi qu’`a chaque client et de programmer une séance de coiffeur dormeur en Java.

L’utilisation des sémaphores est demandée pour garantir l’exclusion mutuelle parmi les threads.

Exercice : Coiffeur dormeur (producteur/consommateur avec

sémaphore)

Semaphore ChaisesLibres = new Semaphore (nbChaises);Semaphore Reveiler = new Semaphore(0); // pas de jeton, donc il faut qu’un release devance le acquireSemaphore ChaiseCoiffeur = new Semaphore(0);

Clients

Salle d’attente

Coiffeur

ChaisesLibres.acquire()

ChaisesLibres.release()

Reveiler.acquire()

Revei

ler.r

elea

se()

1

1

2 ChaiseCoiffeur.acquire()

3

Chaise

Coiffeu

r.rel

e

ase(

)

5

4

Nom

ChaisesLibres

Reveiler

ChaiseCoiffeur

Reveiler

ChaiseCoiffeur

Interblocagepublic class Interblocage { public static void main(String[]

args) { final int[] tab1 = { 1, 2, 3, 4 }; final int[] tab2 = { 0, 1, 0, 1 }; final int[] tabAdd = new int[4]; final int[] tabSub = new int[4];

Thread tacheAdd = new Thread() {

public void run() {synchronized(tab1) {System.out.println("Thread tacheAdd lock tab1");

travailHarassant(); synchronized(tab2) { System.out.println

("Thread tacheAdd lock tab2"); for (int i=0; i<4 ; i++) tabAdd[i] = tab1[i] + tab2[i]; } } } };

Thread tacheSub = new Thread() { public void run() { synchronized(tab2) { System.out.println

("Thread tacheSub lock tab2"); travailHarassant(); synchronized(tab1) { System.out.println

("Thread tacheSub lock tab1"); for (int i=0; i<4 ; i++) tabAdd[i] = tab1[i] - tab2[i]; } } } }; tacheAdd.start(); tacheSub.start(); } static void travailHarassant() {try{Thread.sleep((int)(Math.random()*50+25));} catch (InterruptedException e) {} }}

La classe interne anonyme utilise le motclé new suivi d'un nom de classe ou interfaceque la classe interne va respectivementétendre ou implémenter

Interblocage

Supposons que tab1 et tab2 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 tab1 et tab2.

Le problème d'interblocage est que : La tacheAdd a obtenu le lock de tab1 et attend pour obtenir celui

de tab2, puis ils les libérera tous les 2. La tacheSub a obtenu le lock de tab2 et attend pour obtenir celui

de tab1, puis ils les libérera tous les 2. Ils sont donc bloqués, c'est un Deadlock.

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 tacheAdd lock tab1Thread tacheSub lock tab2<ctrl-C>


Top Related