Vous êtes sur la page 1sur 36

Les threads JAVA

1/27/2021 Bilal Chebaro 1


La synchronisation

1/27/2021 Bilal Chebaro 2


Le problème du producteur consommateur

class Q{

int n;

synchronized int get() {

return n;

synchronized void put(int n) {

this.n = n;

1/27/2021 Bilal Chebaro 3



class Producteur implements Runnable {
Q q;
Producteur(Q q) {
this.q = q ;
new Thread(this, "Producteur").start();
}
public void run() {
int i = 0;
while(true) q.put(i++);
}
}
class Consommateur implements Runnable {
Q q;
Consommateur(Q q) {
this.q = q ;
new Thread(this, "Consommateur").start();
}
public void run() {
while(true) q.get();
}
}
1/27/2021 Bilal Chebaro 4

class PC {

public static void main(String args[]) {

Q q = new Q();

newProducteur(q);

newConsommateur(q);

System.out.println("Press Control-C to stop.");

1/27/2021 Bilal Chebaro 5


 Les problèmes de synchronisation se traduisent souvent en terme


d’attente

 L'exécution, par un thread, d'une méthode d'un objet partagé ne peut


continuer que si une certaine condition est vérifiée

 Case pleine, Case vide, etc ;

 Il y a principalement deux formes d'attente possibles

 L’attente active

 Le thread boucle et teste à chaque itération la condition

o Consomme le temps UC : pas efficace et à écarter ;

1/27/2021 Bilal Chebaro 6


 L’attente bloquée (passive)

 Le thread est mis dans un état bloqué pendant toute la durée de l’attente

o Pas d’allocation de l’UC, donc pas de consommation de temps machine ;

 Le déblocage d’un thread se fait de manière ciblée

o Par un autre thread : solution programmée ;

o Par le système : solution automatique

» Quand il n’y a aucune raison que le thread continue à attendre ;

1/27/2021 Bilal Chebaro 7


 Java n'utilise aucune de ces 2 formes

 Java utilise une forme intermédiaire : l’attente active contrôlée

 Simplifier la programmation : le programmeur ne se soucie pas du déblocage

o Déblocage aveugle et non ciblé

» Tous les threads en attente dans un moniteur Java sont débloqués

 Mais, Ils doivent réévaluer la condition d’attente ;

1/27/2021 Bilal Chebaro 8


Les moniteurs Java

 La solution Java est très proche de la solution avec les Régions


Critiques Conditionnelles (RCC)

 Mais, c’est au programmeur d’écrire la boucle d’attente ;

 Chaque objet fournit un mécanisme pour en faire une zone d’attente

 Il dispose des méthodes « wait », « notify » et « notifyAll »

o En général, leurs appels sont placés dans des méthodes « synchronized »

» L'exception « IllegalMonitorState » est déclenchée quand le thread appelle une


méthode sur un objet dont elle ne possède pas le verrou

 La synchronisation et l’EM sont 2 mécanismes très liés ;

1/27/2021 Bilal Chebaro 9


 La méthode « wait »

 Met le thread appelant dans l’état bloqué ;

 Libère de façon atomique le verrou associé au bloc (méthode)


« synchronized » ;

 Le thread, quand il sera débloqué, devra redemander le verrou (EM), en


compétition avec les autres threads

 Débloqués d’une méthode wait() du même objet partagé ;

 Des nouveaux qui souhaitent exécuter une méthode de l’objet partagé ;

 C’est le système qui redemande l’EM

o N’est pas à la charge du programmeur ;

1/27/2021 Bilal Chebaro 10


 La méthode notifyAll ()

 Débloque tous les threads bloqués dans l’exécution de la méthode « wait »


dans le même objet partagé

 Le thread ayant la plus grande priorité s’exécute en premier ;

 Elle ne libère pas tout de suite le verrou de l’EM

 Libéré quand le thread sort de la méthode « synchonized » ;

1/27/2021 Bilal Chebaro 11


 Il est parfois simple d'éviter l'utilisation du déblocage aveugle en


utilisant la méthode notify()

 La méthode notify() débloque un thread bloqué suite à l’exécution de wait()


dans un objet partagé

 Lequel : le premier ayant exécuté wait () dans l’objet ;

 Mais elle ne libère pas tout de suite la SC (le verrou)

o Libérée lorsque le thread sort de la méthode « synchronized » ;

 notify () n’a aucun effet lorsqu’il n’y a pas de thread bloqué sur un wait () ;

1/27/2021 Bilal Chebaro 12


 Les méthodes « wait() », « notify() » et « notifyAll() » sont


implémentées comme « final » dans la classe « Object »

 Elles sont alors disponibles dans toutes les classes ;

 Elles ne sont utilisables que dans des blocs « synchronized » ;

 Les déclarations de ces méthodes dans la classe « Object »

 public final void wait( ) throws InterruptedException ;

 public final void notify( ) ;

 public final void notifyAll( ) ;

 Il existe des versions de « wait » qui permettent de spécifier le temps d’attente ;

1/27/2021 Bilal Chebaro 13


Le problème du producteur consommateur : implémentation correcte
class Q{
int n;
boolean nouvelleValeur = False ;
synchronized int get() {
if (! nouvelleValeur ) wait() ;
nouvelleValeur = False ;
notify() ;  Problème à la compilation de cette version
return n;  Pas de traitement associé à l’exception
InterruptException que wait peut déclencher suite
} à un appel à interrupt()
synchronized void put(int n) {
if (nouvelleValeur ) wait();
this.n = n;
nouvelleValeur = True ;
notify() ;
}
}
1/27/2021 Bilal Chebaro 14

 Le compilateur n’accepte pas la solution précédente

 Pas de traitement associé à l'exception InterruptedException

 Exception susceptible d’être lancée par « wait » quand le thread est interrompu
par l’appel à la méthode « interrupt () »

o public void wait () throws InterruptedException ;

 Quand on se contente de placer l'instruction « wait » dans un bloc


catch vide
try { wait() ; } catch (InterruptedException e) { } ;

 On masque le déclenchement de l’exception

 Simple accommodement aux exigences du compilateur ;

1/27/2021 Bilal Chebaro 15


Le problème du producteur consommateur : implémentation correcte
class Q{
int n;
boolean nouvelleValeur = False ;
synchronized int get() {
if (! nouvelleValeur ) try {
wait() ;
} catch(InterruptException e ){
System.out.println(”InterruptException capturée”);
}
nouvelleValeur = False ;
notify() ;
return n;
}
synchronized void put(int n) {
if (nouvelleValeur ) try {
wait();
} catch(InterruptException e ){
System.out.println(”InterruptException capturée”);
}
this.n = n;
nouvelleValeur = True ;
notify() ;
}
}
1/27/2021 Bilal Chebaro 16

class Producteur implements Runnable {

Q q;

Producteur(Q q) {

this.q = q ;

new Thread(this, "Producteur").start();

public void run() {

int i = 0;

while(true) q.put(i++);

}
1/27/2021 Bilal Chebaro 17

class Consommateur implements Runnable {

Q q;

Consommateur(Q q) {

this.q = q ;

new Thread(this, "Consommateur").start();

public void run() {

while(true) q.get();

1/27/2021 Bilal Chebaro 18


class PC {

public static void main(String args[]) {

Q q = new Q();

new Producteur(q);

new Consommateur(q);

System.out.println("Press Control-C to stop.");

1/27/2021 Bilal Chebaro 19


 Le déblocage aveugle est cause potentielle de baisse de


performance

 Des threads risquent d’être débloqués pour rien

 Les déblocages et les blocages à nouveaux sont coûteux

o Restitution/constitution du contexte du thread ;

o Prise de verrou sur l’objet partagé ;

o Déroulement de l’algorithme de l’ordonnanceur ;

1/27/2021 Bilal Chebaro 20


 L’entier déposé dans le buffer intéresse le thread consommateur ;

 La place libérée en retirant un entier intéresse le thread


producteur ;

 Le tampon est soit vide, soit plein

 Quand il est vide, seul le consommateur peut être en attente ;

 Quand il est plein, seul le producteur peut être en attente ;

 notify() qui débloque au plus un seul thread fonctionne


correctement dans ce cas ;

1/27/2021 Bilal Chebaro 21


 Mais dans d’autres cas (plusieurs producteurs et plusieurs


consommateurs

 On peut se trouver avec des producteurs et des consommateurs en attente

 Donc, avec un déblocage mal ciblé, on a la possibilité d’avoir des threads


bloqués alors que leur condition de passage est satisfaite ;

 Faire très attention avec la méthode notify

 La méthode notify peut être utilisée lorsqu’il n’y a qu’un seul cas d’attente

 Tous les threads en attente attendent la même condition ;

1/27/2021 Bilal Chebaro 22


Exemple : implémentation des sémaphores

public class Semaphore {


protected int valeur = 0 ;
protected Semaphore () { valeur = 0 ;}
protected Semaphore (int initial) { valeur = initial ;}
public synchronized void P() {
valeur -- ;
if (valeur < 0) {
try {
wait() ;
} catch (InterruptedException e) {
System.err.println(“Semaphore.P() InterruptedException: déni de serrvice”);
}
}
}

1/27/2021 Bilal Chebaro 23



public synchronized void V() {
valeur ++ ;
if (valeur <= 0) notify() ;
}
public synchronized int getValeur () {
return valeur ;
}
public synchronized String toString() {
return String.valueOf(valeur);
}
public synchronized void tryP() throws WouldBlockException {
if (valeur > 0) this.P() ;
else throw new WouldBlockException() ;
}
}
1/27/2021 Bilal Chebaro 24

 La méthode tryP() permet d'éviter toute attente, ce que ne permet


pas la programmation suivante

if (S.getValeur () > 0)
//Un autre thread peut faire l’opération P juste en ce point
S.P() ; // et entraîner, de ce fait, le blocage non voulu sur ce P
else …..

1/27/2021 Bilal Chebaro 25


Le problème de producteur-consommateur avec un tampon de N cases

class ProdConsMonitor implements ProdConsInterface {


private int tampon[];
private int queue, tete, N, NbPleins = 0, NbVides = 0 ;
ProdConsMonitor (int n) {
N=n;
tampon = new int [N] ;
}
}

1/27/2021 Bilal Chebaro 26


public synchronized void Déposer (int m) throws InterruptedException {


while (NbPleins == N) wait();
tampon[queue] = m ;
queue = (queue +1) % N ;
NbPleins ++;
System.out.println(Thread.currentThread().getName() +
“vient de produire” + m) ;
notifyAll () ;
}

1/27/2021 Bilal Chebaro 27


public synchronized void Prélever( ) throws InterruptedException {


while (NbPleins == 0) wait();
int m = tampon[tête] ;
tête = (tête+1) % N ;
NbPleins -- ;
notifyAll() ;
System.out.println(Thread.currentThread().getName() +
“vient de consommer” + m) ;
return m ;
}

1/27/2021 Bilal Chebaro 28


...

class Producteur extends Thread {


ProdConsMonitor Mo ;
Producteur (ProdConsMonitor Mo) {
this.Mo = Mo ;
}
public void run() {
try {
while (true) {
int m= (int)(1000*Math.random());
Mo.Déposer(m) ;
Thread.sleep((int)(1000*Math.random()));
}
} catch(InterruptedException e) { }
}

1/27/2021 Bilal Chebaro 29


class Consommateur extends Thread {


ProdConsMo Mo ;
Consommateur (ProdConsMonitor Mo){
this.Mo = Mo ;
}
public void run() {
try {
while (true) {
int m = Mo.Prélever( ) ;
Thread.sleep((int)(1000*Math.random())) ;
}
} catch(InterruptedException e) { }
}
}

1/27/2021 Bilal Chebaro 30


//La classe main : à exécuter avec un argument = taille du tampon


class ProdConsMain {
public static void main(String argv[]) {
int N ;
try{
N = Integer.parseInt(argv[0]) ;
} catch(ArrayIndexOutOfBoundsException exp) {
System.out.println("USAGE:java ProdConsMain [taille du tampon]") ;
System.exit(0) ;
}
ProdConsMonitor pc = new ProdConsMonitor(N) ;
Producteur producteur = new Producteur (pc) ;
Consommateur consommateur = new Consommateur (pc) ;
producteur.setName(“Producteur”) ;
producteur.start() ;
consommateur.setName("Consommateur") ;
consommateur.start() ;
producteur.join() ;
consommateur.join() ;
}
}

1/27/2021 Bilal Chebaro 31


 Inconvénients des moniteurs Java <= 1.4

 On ne peut pas bloquer un thread sur une condition particulière ;

 On ne peut pas réveiller un thread particulier

 Tous les threads bloqués sont dans la file d’accès au moniteur ;

 A partir de la version 1.5, un ensemble d’utilitaires relatifs à la


concurrence sont rajoutés dont

 java.util.concurrent.locks : qui fournit des mécanismes de verrouillage


(verrous de lect/écrit, variables conditions) ;

 java.util.concurrent : qui fournit entre autres une classe Semaphore


qui permet la synchronisation à l’aide des sémaphores ;

1/27/2021 Bilal Chebaro 32


Les sémaphores

1/27/2021 Bilal Chebaro 33


Les sémaphores

Package java.util.concurrent ;

public class Semaphore extends Object implements Serializable {

Semaphore(int permits) ; //nombre initial des tickets : nombre d’appels non bloquant

Semaphore(int permits, boolean fair) ; //fair == true pour garantir une gestion FIFO

public void acquire () ; //Demande d’une permission ou d’un ticket

public void acquire (int permits) ; //Demande d’un certain nombre de tickets

public void release() ; //Rendre un ticket

public void release(int permits) ; //Rendre un certain nombre de tickets

1/27/2021 Bilal Chebaro 34


Exemple d’utilisation : l’Exclusion Mutuelle

Semaphore s = new Semaphore (1, true);

s.acquire();

//Accès à la section critique;

s.release();

1/27/2021 Bilal Chebaro 35


Exemple d’utilisation : le problème de producteur-consommateur

import java.util.concurrent.* ;

interface ProdConsInterface {

public void Déposer(int m) throws InterruptedException ;

public int Prélever() throws InterruptedException ;

1/27/2021 Bilal Chebaro 36

Vous aimerez peut-être aussi