Download La programmation concurrente en Java

Document related concepts
no text concepts found
Transcript
La programmation
concurrente en Java
Les Threads
Le problème (1)
• Java permet le multitasking
• Il le fait via les threads (processus légers)
• Divers threads partagent le même espace
mémoire mais pas les registres
• Quand la JVM donne la main à un thread, il load
ses registres et stocke ceux du thread
précédent.
• Exemple :
thread1
compte1.virer(100, compte2);
thread2
compte2.virer(100, compte1);
Le problème (2)
• En code exécutable, cela donne pour le premier
thread :
R1 = compte1.solde;
R1 -= 100;
compte1.solde = R1;
R1 = compte2.solde;
R1 += 100;
compte2.solde = R1
Le problème (3)
• L’exécution des 2 threads pourra donner :
thread 1
R1 = compte1.solde;
R1 -= 100;
compte1.solde = R1;
thread2
R1 = compte2.solde
R1 -= 100;
R1 = compte2.solde;
R1 += 100;
compte2.solde = R1
compte2.solde = R1;
R1 = compte1.solde;
R1 += 100;
compte1.solde = R1
Le problème (4)
• Après l’exécution le compte1 retrouve le même
solde que précédemment. 
• Par contre le compte2 voit son solde diminué de
100 ! 
• Il aurait fallu que le retrait et le dépôt soient des
opérations atomiques.
Créer un Thread (1)
• 1ère méthode :
• Définition :
public class MonThread extends Thread {
public void run() {
…..
}
}
• Exécution :
Thread thread = new MonThread();
thread.start();
• Attention : ne pas appeler run(); start() exécute run()
dans un autre thread (unité d’exécution)
Créer un Thread (2)
• 2ème méthode :
• Définition :
public class MonRunnable implements Runnable {
public void run() {
…..
}
}
• Exécution :
Thread thread = new Thread(new MonRunnable());
thread.start();
• Attention : ne pas appeler run()
• Thread est le contrôleur et Runnable la tâche
Cycle de vie
• Le Thread existe depuis qu’on a appelé son
constructeur
• Avant d’appeler start(), on pourra procéder à des
initialisation
• Après qu’il ait terminé l’exécution de run(), le
Thread continue à exister. On pourra en profiter
pour récupérer des résultats
Méthodes
•
•
•
•
•
•
•
•
•
•
static Thread currentThread();
static void sleep(long millis);
static void yield(); // passe la main
static boolean interrupted(); // clear status
void run();
void start();
void interrupt();
boolean isInterrupted(); // keep status
void join(); // thread.join() attend la fin de thread
InterruptedException // clear status
Arrêter un Thread (1)
• un Thread s’arrête quand il termine l’exécution
de run();
• Si la décision de terminer est extérieure : 2
manières
– en testant une condition
– par interruption
Arrêter un Thread (2)
• En testant une condition :
public class MonThread extends Thread {
private volatile boolean fini = false;
public void run() {
while (! fini) {
…..
}
}
public void terminer() {
fini = true;
}
}
Arrêter un Thread (3)
• En interrompant le Thread :
public class MonThread extends Thread {
public void run() {
while (! isInterrupted()) {
…..
}
}
}
Locking
• Chaque objet possède un lock (un moniteur)
• Si une méthode ou un bloc est synchronized, il
faudra acquérir le lock de l’objet avant de
l’exécuter
• Si le lock est libre, on y va
• Si le lock est acquis par un autre thread, on
attend qu’il se libère
• Si dans une méthode ou un block synchronized,
on appelle une autre méthode synchronized, il
ne faudra pas acquérir le lock une deuxième fois
Méthode synchronized
• public synchronized int getCompteur() {
return compteur;
}
public synchronized increment() {
compteur++;
}
Bloc synchronized
• public void incrementAfterOneSecond() {
try {
Thread.sleep(1000); // pas dans le synchronized
// car sleep ne perd pas le lock
} catch (InterruptedException ie) {
}
synchronized (this) {
compteur++;
}
}
Méthode ou bloc static
synchronized (1)
• Une méthode static synchronized ne peut pas,
bien évidemment, obtenir un lock sur this.
• Elle obtient un lock sur un autre objet : celui
représentant la classe dans Class.
public class CompteTout {
private static int cpt = 0;
public static synchronized void incr() {
cpt++;
}
…..
}
acquiert une lock sur CompteTout.class
Méthode ou bloc static
synchronized (2)
• Une méthode non static accédant à cpt devra acquérir le
lock sur le même objet donc
Faux :
public synchronized int getCpt() {
return cpt;
}
Correct :
public int getCpt() {
synchronized(CompteTout.class) {
return cpt;
}
}
volatile
• Un champ peut être déclaré volatile
• dans ce cas au moment d’un changement de thread, la
JVM stocke un registre contenant cette variable dans la
variable elle-même
• le champ doit être sur 32 bits maximum
• pas valable pour une adresse (tableau, objet) : ce serait
l’adresse qui serait mis à jour pas ce qu’elle désigne
• évite de mettre synchronized des méthodes qui ne font
que des lectures ou des affectations simples (=) du
champ
• pas de ++ ou de += …..
Deadlock (1)
• Un deadlock arrive si deux ou plusieurs threads
tentent d’acquérir un lock détenu par un autre
qui ne pourra pas le libérer pour l’une ou l’autre
raison
• exemple : MonDeadlock
Deadlock (2)
public class MonDeadlock extends Thread {
private boolean fini = false;
public synchronized void run() {
while (!fini) {
System.out.println("je run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
public synchronized void terminer() {
fini = true;
}
}
Deadlock (3)
• Solution :
public class MonThread extends Thread {
private boolean fini = false;
public void run() {
while (!getFini()) {
System.out.println("je run");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
}
}
private synchronized boolean getFini() {
return fini;
}
public synchronized void terminer() {
fini = true;
}
}
Comment synchroniser (1)
• Repérer dans vos classes les champs qui peuvent
être modifiés et sont accédés par plusieurs threads.
• mettre synchronized (sur this) les parties des
méthodes qui accèdent à ces champs
• si plusieurs champs d’un même objet sont accédés
dans la même méthode se demander si l’ensemble
de ces accès doit être atomique
• si c’est le cas synchroniser (sur this) cet ensemble
(synchroniser éventuellement la méthode)
Comment synchroniser (2)
• si des méthodes d’objets différents (de classes
différentes) sont appelées dans une méthode,
se demander si l’ensemble de ces accès doit
être atomique.
• si non, ne pas synchroniser les appels des
méthodes sur les objets extérieurs :
public class Portefeuille {
public versement(double montant, Compte c) {
synchronized (this) { this.contenu -= montant; }
c.dépot(montant) // où dépôt est une méthode synchronized
}
Comment synchroniser (3)
• si oui, ordonner les objets (acquérir les locks toujours
dans le même ordre)
public void transférer (Portefeuille p, Compte c) {
synchronized(p) {
synchronized(c) {
c.dépot(p.getContenu());
}
}
}
• une autre méthode qui devrait locker les deux même
objets le ferait dans le même ordre
• Ici c’est simple il suffit d’ordonner les classes
Comment synchroniser (4)
• si des champs d’objets différents (de la même
classe) sont accédés dans la même méthode,
se demander si l’ensemble de ces accès doit
être atomique.
• si non, synchroniser séparément les accès des
champs des divers objets
public virement(double montant, Compte c) {
synchronized (this) { this.solde -= montant; }
synchronized(c) { c.solde += montant; }
}
Comment synchroniser (5)
• si oui, ordonner les objets sur lesquels on
synchronise : par exemple si on a
public static int cpt = 0;
public int monCpt = 0;
public synchronized static incr() {
cpt++;
}
public synchronized static decr() {
cpt--;
}
Comment synchroniser (6)
• si on veut que l’ensemble des deux incrémentations soit
atomique, on fera :
public synchronized void incrémenter() {
synchronized (CompteTout.class) {
incr();
monCpt++;
}
}
• on a acquis d’abord le lock sur this puis sur
CompteTout.class
• une autre méthode (décrémenter, par exemple) devrait
les acquérir dans le même ordre
Comment synchroniser (7)
• Le cas le plus difficile est quand on doit
synchroniser deux objets de la même classe
• Il faut alors ordonner ces objets
• parfois un champs de l’objet le permet
• sinon on utilisera System.identityHashCode()
Comment synchroniser (8)
• Si on doit locker les deux comptes lors d’un virement on
pourra utiliser le numéro de compte:
public virement(double montant, Compte c) {
if (this.numCompte < c.numCompte)
synchronized (this) { synchronized(c) {
this.solde -= montant; c.solde += montant;
}}
else
synchronized (c) { synchronized(this) {
this.solde -= montant; c.solde += montant;
}}
}
Attendre une ressource
• La méthode wait() héritée d’Object
• Doit être appelée sur un objet dont on a acquis
le lock
• Donc dans une méthode synchronized ou un
bloc synchronized sur cet objet
• le thread courant est placée sur une file
d’attente liée à cet objet
• il libère le lock sur l’objet durant le wait();
Prévenir de la disponibilité d’une
ressource
• Les méthodes notify() et notifyAll() héritées
d’Object
• Doit être appelée sur un objet dont on a acquis
le lock
• Donc dans une méthode synchronized ou un
bloc synchronized sur cet objet
• notify() libère un thread en attente sur la file liée
à l’objet
• notifyAll() les libère tous mais un seul passera
Bon usage de wait/notify
• notify réveille un thread, mais pas
nécessairement le bon
• Il vaut mieux dans la plupart des cas
utiliser notifyAll
• Ne pas faire if (! condition) wait();
– si on est réveillé alors que condition n’est pas
vraie on cesse d’attendre
• Faire while (! condition) wait();
Double wait()
•
•
•
Ne pas faire :
wait(); wait();
Car deux notify() pourraient se produire avant
de sortir du premier wait(); le 2nd notify est
alors perdu
Faire
while (nombreDeNotify < 2) wait();
Être sur de notifier
• Pour être certain que le notify soit fait et qu'on
libère bien ceux qui attendent même en cas de
problème, toujours faire le notify dans un bloc
finally
try {
…
} finally {
notifyAll(); // ou notify(); selon les cas
}
Double-checked Locking : NON
• Comment synchroniser une lazy initialization
• exemple (un singleton) :
public static Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
Double-checked Locking : NON
• le double-checked locking ne fonctionne pas :
public static Singleton getInstance() {
if (instance == null)
synchronized(Singleton.class) {
if (instance == null)
instance = new Singleton();
}
return instance;
}
• un autre thread risque de ne pus trouver instance à null
avant la fin de l’initialisation !
Double-checked Locking : NON
• Solution : synchroniser la méthode
public static synchronized Singleton getInstance() {
if (instance == null)
instance = new Singleton();
return instance;
}
wait(), sleep() et synchronisation
• On essaiera de libérer tous les moniteurs (et
autres locks) avant de faire un wait() (sauf celui
de l’objet sur lequel on wait();). Il y a un énorme
risque de deadlock à ne pas le faire.
• l’usage de variable locale est parfois utile,
celles-ci ne devant pas être synchronisées
• Dans le même ordre d’idée, on libérera tous les
moniteurs, locks, … avant de faire un sleep();
Timer et TimerTask (1)
• Dans un contexte non graphique n’utilisez pas
javax.swing.Timer !!!
• Ici aussi, Timer est le contrôleur et TimerTask, la
tâche
class Tâche extends TimerTask {
public void run() {
System.out.println("action !");
}
}
Timer et TimerTask (2)
• Timer timer = new Timer();
timer.schedule(new Tâche(), 5000);
// la tâche s’exécutera dans 5 secondes
timer.schedule(new Tâche(), 5000, 1000);
// la tâche s’exécutera dans 5 secondes puis toutes les
// secondes (en temps relatif, décalage possible)
timer.scheduleAtFixedRate(new Tâche(), 5000, 1000);
// la tâche s’exécutera dans 5 secondes puis toutes les
// secondes (en temps absolu, non exécution possible)
timer.cancel() // plus d’exécution après ça
Concurrence en Java 5.0
• Le package java.util.concurrent fournit des
classes utilitaires :
–
–
–
–
–
–
–
–
Semaphore
CyclicBarrier
CountDownLatch
Exchanger
diverses implémentation de l’interface BlockingQueue
l’interface Callable
FutureTask
Executor, ExecuterService et ThreadPoolExecutor
BlockingQueue
•
•
•
•
•
•
boolean add(E e) -> IllegalStateException si plein
E remove() -> NoSuchElementException si vide
boolean offer(E e) -> false si plein
E poll() -> null si vide
void put(E e) -> attend si plein
E take() -> attend si vide
BlockingQueue : producteurconsommateur (1)
public class Producteur implements Runnable {
private BlockingQueue<Integer> queue;
private Random random = new Random();
public Producteur(BlockingQueue<Integer> queue) {
this.queue = queue;
}
public void run() {
while (true) {
try {
queue.put(random.nextInt(1000));
} catch (InterruptedException e) {
}
}
}
}
BlockingQueue : producteurconsommateur (2)
public class Consommateur implements Runnable {
private BlockingQueue<Integer> queue;
public Consommateur(BlockingQueue<Integer> queue) {
this.queue = queue;
}
public void run() {
while (true) {
try {
System.out.println(queue.take());
} catch (InterruptedException e) {
}
}
}
}
BlockingQueue : producteurconsommateur (3)
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
public class Main {
public static void main(String[] args) {
BlockingQueue<Integer> queue = new
ArrayBlockingQueue<Integer>(5);
new Thread(new Producteur(queue)).start();
new Thread(new Consommateur(queue)).start();
}
}
Semaphore : théorie
•
•
•
•
•
•
•
Semaphore( int nombreDePermis); // 1 == binaire
Semaphore (int nombreDePermis, boolean FIFO);
void acquire(); // aussi avec un nbPermis
void acquireUninterruptibly(); // aussi avec un nbPermis
void release(); // aussi avec un nbPermis
boolean tryAcquire(); // aussi avec un nbPermis
boolean tryAcquire(long timeout, TimeUnit unités);
// aussi avec un nbPermis (en 1er paramètre)
Semaphore (1)
public class Noeud implements Cloneable {
private int valeur;
private Noeud suivant;
private Semaphore semaphore = new Semaphore(1);
public Noeud(int valeur) {
this(valeur, null);
}
public Noeud(int valeur, Noeud suivant) {
this.valeur = valeur;
this.suivant = suivant;
}
public Noeud getSuivant() {
return suivant;
}
Semaphore (2)
public void setSuivant(Noeud suivant) {
this.suivant = suivant;
}
public int getValeur() {
return valeur;
}
public void setValeur(int valeur) {
this.valeur = valeur;
}
public Semaphore getSemaphore() {
return semaphore;
}
}
Semaphore (3)
public class Liste {
private Noeud tête;
public boolean estVide() { return tête == null; }
public String toString() {
try {
String rés = "[ ";
for (Noeud n = tête; n != null; n = n.getSuivant()) {
n.getSemaphore().acquire();
rés += n.getValeur() + " ";
n.getSemaphore().release();
}
rés += "]";return rés;
} catch (InterruptedException e) { return null; }
}
Semaphore (4)
public boolean ajouterNoeud(int valeur) {
Noeud n = new Noeud(valeur);
if (estVide()) { tête = n; return true; }
try {
Noeud prec = null;
Noeud noeud = tête;
for (; noeud != null; noeud = noeud.getSuivant()) {
noeud.getSemaphore().acquire();
if (n.getValeur() == noeud.getValeur()) {
if (prec != null)
prec.getSemaphore().release();
noeud.getSemaphore().release();
return false;
}
Semaphore (5)
if (n.getValeur() < noeud.getValeur()) {
if (prec == null) {
n.setSuivant(noeud); tête = n;
noeud.getSemaphore().release(); return true;
}
break;
}
if (prec != null) prec.getSemaphore().release();
prec = noeud;
}
n.setSuivant(noeud); prec.setSuivant(n);
prec.getSemaphore().release();
if (noeud != null) noeud.getSemaphore().release();
return true;
} catch (InterruptedException e) { return false;}
}
Semaphore (6)
public boolean supprimerNoeud(int valeur) {
if (estVide())
return false;
try {
Noeud prec = null;
Noeud noeud = tête;
for (; noeud != null; noeud = noeud.getSuivant()) {
noeud.getSemaphore().acquire();
if (valeur == noeud.getValeur()) {
if (prec == null) {
tête = tête.getSuivant();
noeud.getSemaphore().release();
return true;
}
Semaphore (7)
prec.setSuivant(noeud.getSuivant());
noeud.setSuivant(null);
prec.getSemaphore().release();
noeud.getSemaphore().release();
return true;
} // fin du if ==
if (prec != null) prec.getSemaphore().release();
prec = noeud;
}
prec.getSemaphore().release();
if (noeud != null) noeud.getSemaphore().release();
} catch (InterruptedException e) { }
return false;
}
Semaphore (8)
public class Main {
public static void main(String[] args) {
final Random random = new Random();
final Liste liste = new Liste();
new Thread() {
public void run() {
int cpt = 0;
while (true) {
liste.ajouterNoeud(random.nextInt(10));
System.out.println(++cpt + ") " + liste);
}
}
}.start();
Semaphore (9)
new Thread() {
public void run() {
int cpt = 0;
while (true) {
liste.supprimerNoeud(random.nextInt(10));
System.out.println(++cpt + ". " + liste);
}
}
}.start();
}
}
CyclicBarrier : théorie
• CyclicBarrier(int nbParties);
• CyclicBarrier(int nbParties, Runnable action);
• int await(); // à faire nbParties fois pour qu’action
démarre (existe aussi avec un timeout)
• reset(); // pour réutiliser la barrière
CyclicBarrier (1)
public class SommeLigne extends Thread {
private int[] ligne; private int[] résultat; private int indice;
private CyclicBarrier barrier;
public SommeLigne(CyclicBarrier barrier, int[] ligne, int[] res, int idx) {
this.barrier = barrier;
this.ligne = ligne; this.résultat = res; this.indice = idx;
}
public void run() {
for (int i = 0; i < ligne.length; i++)
résultat[indice] += ligne[i];
try {
barrier.await();
} catch (InterruptedException e) {
} catch (BrokenBarrierException e) {
}
}
}
CyclicBarrier (2)
public class Somme implements Runnable{
private int[] résultats;
private int somme;
public Somme(int[] résultats) {
this.résultats = résultats;
}
public void run() {
for (int i = 0; i < résultats.length; i++)
somme += résultats[i];
System.out.println("La somme vaut : " + somme);
}
}
CyclicBarrier (3)
public class Main {
public static void main(String[] args) {
int [][] matrice = { { 1 },
{ 2, 2 },
{ 3, 3, 3 },
{ 4, 4, 4, 4 },
{ 5, 5, 5, 5, 5 } };
int nbLignes = matrice.length;
int [] résultats = new int[nbLignes];
CyclicBarrier barrier = new CyclicBarrier(nbLignes,
new Somme(résultats));
for (int i = 0; i < nbLignes; i++)
new SommeLigne(barrier, matrice[i], résultats, i).start();
}
CountDownLatch : théorie
• CountDownLatch(int compteur);
• void await(); // attends qu’on ait fait compteur
countdown() (existe aussi avec un timeout)
• void countdown(); // à faire compteur fois pour
terminer
CountDownLatch (1)
public class SommeLigne extends Thread {
private int[] ligne, résultat;
private int indice;
private CountDownLatch startLatch, stopLatch;
public SommeLigne(CountDownLatch startLatch, CountDownLatch
stopLatch, int[] ligne, int[] res, int idx) {
this.startLatch = startLatch; this.stopLatch = stopLatch;
this.ligne = ligne; this.résultat = res; this.indice = idx;
}
public void run() {
try { startLatch.await(); } catch (InterruptedException e1) { }
for (int i = 0; i < ligne.length; i++) résultat[indice] += ligne[i];
stopLatch.countDown();
}
}
CountDownLatch (2)
public class Main {
public static void main(String[] args) {
int [][] matrice = { { 1 },
{ 2, 2 },
{ 3, 3, 3 },
{ 4, 4, 4, 4 },
{ 5, 5, 5, 5, 5 } };
int nbLignes = matrice.length; int [] résultats = new int[nbLignes];
CountDownLatch startLatch = new CountDownLatch(1);
CountDownLatch stopLatch = new CountDownLatch(nbLignes);
for (int i = 0; i < nbLignes; i++)
new SommeLigne(startLatch, stopLatch, matrice[i],
résultats, i).start();
startLatch.countDown();
try { stopLatch.await(); } catch (InterruptedException e) { }
new Thread(new Somme(résultats)).start();
}
Callable <V>
•
•
•
•
Semblable à Runnable
ne définit pas de run()
mais une méthode V call();
on l’utilisera avec une FutureTask
Callable<Integer>
public class SommeCall implements Callable<Integer> {
private int[] résultats;
private int somme;
public SommeCall(int[] résultats) {
this.résultats = résultats;
}
public Integer call() throws Exception {
for (int i = 0; i < résultats.length; i++)
somme += résultats[i];
return somme;
}
}
FutureTask<V> : théorie
• implémente Runnable et Future<V>
•
•
•
•
FutureTask<V>(Callable<V> callable);
FutureTask<V>(Runnable runn, V résultat);
void run() appelle call() de callable ou run() de runn
V get() attend la fin de run puis renvoie le résultat de call
ou celui passé en paramètre
• isDone();
• cancel();
• isCancelled();
FutureTask<Integer> (1)
• utilisation avec une CyclicBarrier :
FutureTask ft = new FutureTask<Integer>(
new SommeCall(résultats)));;
barrier = new CyclicBarrier(nbLignes, ft);
for (int i = 0; i < nbLignes; i++)
new SommeLigne(barrier, matrice[i], résultats, i).start();
try {
System.out.println("la somme = " + ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
FutureTask<Integer> (2)
• utilisation seule après l’emploi d’un CountDownLatch par
exemple :
FutureTask ft = new FutureTask<Integer>(
new SommeCall(résultats));
ft.run(); // ou new Thread(ft).start();
try {
System.out.println("la somme = " + ft.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
Exchanger<V> : théorie
• Exchanger()
• V exchange(V valeur)
attend qu’un autre thread appelle exchange sur
cet Exchanger et échange les valeur des deux
threads
existe aussi avec un timeout
Exchanger<List<Integer>>
• Un producteur remplit un buffer d’entier.
• Quand il est plein, il échange ce buffer avec un
consommateur.
• Un consommateur vide un buffer plein
• Quand il est vide il échange son buffer avec
celui du producteur
• Voir code sur l’extranet
Executor et ExecutorService :
théorie
• Executor : Interface qui ne définit qu’une seule méthode
– void execute(Runnable runnable);
• ExecutorService : Interface, extends Executor
– <T> List<Future<T>>
invokeAll(Collection<Callable<T>> tasks);
– <T> List<Future<T>>
invokeAny(Collection<Callable<T>>tasks);
– <T> Future<T> submit(Callable<T> task);
– Future<?> submit(Runnable task);
– <T> Future<T> submit(Runnable task, T résultat);
– void shutdown();
Executor, ExecutorService :
exemple
class NetworkService {
private final ServerSocket serverSocket;
private final ExecutorService pool;
public NetworkService(int port, int poolSize) throws IOException {
serverSocket = new ServerSocket(port);
pool = Executors.newFixedThreadPool(poolSize);
}
public void serve() {
try {
for ( ; ; )
pool.execute(new Handler(serverSocket.accept()));
} catch (IOException ex) { pool.shutdown(); }
}
}
java.util.concurrent.locks
• définit les interfaces
– Lock
– Condition
– ReadWriteLock
• fournit les implémentations
– ReentrantLock
– ReentrantReadWriteLock
Lock : théorie
•
•
•
•
•
•
C'est une interface
void lock(); // acquiert le lock
void lockInterruptibly(); // de manière imterruptible
Condition newCondition();
boolean tryLock(); // acquiert le lock s'il est libre
boolean tryLock(long time, TimeUnit unit); // s'il devient
libre dans le temps indiqué
• void unlock(); // libère le lock
• Elle est implémentée par la classe ReentrantLock
• Exemple : la classe Buffer dans producerconsumer
ReadWriteLock : théorie
• Cette interface fournit 2 méthodes :
– Lock readLock();
– Lock writeLock();
• Une classe qui l'implémente devra garder 2 locks
– l'un partageable pour les opérations de lecture
– l'autre exclusif pour les opérations d'écriture
• Java fournit une implémentation : la classe
ReentrantReadWriteLock. Celle-ci possède 2 classes
emboîtées :
– ReentrantReadWriteLock.ReadLock
– ReentrantReadWriteLock.WriteLock
ReentrantReadWriteLock : théorie (1)
• on ne peut acquérir le readLock que si le writeLock est
libre
• on ne peut acquérir le writeLock que si le writeLock et le
readLock sont libres
• si on a le writeLock on peut acquérir le readLock
• si on a le readLock, on ne peut pas acquérir le
writeLock, on doit d'abord libérer le readLock
• si des lecteurs ont le readLock et qu'un rédacteur
demande le writeLock, plus aucun autre lecteur
n'obtiendra le readLock
ReentrantReadWriteLock : théorie (2)
• ReentrantReadWriteLock()
• ReentrantReadWriteLock(boolean fair)
• Si le lock est construit avec fair == true, quand un
writeLock est libéré, le writeLock sera accordé au
rédacteur qui l'a demandé depuis le plus longtemps, à
moins qu'il n'y ait des lecteurs qui attendent depuis
encore plus longtemps auquel cas l'ensemble des
lecteurs acquérra le readLock
• Exemple : RWLockCoordinator de readerwriter
Prévention des deadlocks
• Un deadlock aura lieu uniquement si 4 conditions sont
réalisées :
–
–
–
–
Exclusion mutuelle
Non préemption
Hold and Wait
Circularité
• Pour prévenir un deadlock, on doit
– soit empêcher la circularité, par exemple en ordonnant les
ressources
– soit empêcher le Hold and Wait, par exemple en acquérrant
toutes les ressources simultanément ou en utilisant le Two
Phase Locking
• Exemples : le dîner des philosophes : voir philosophes