main
) dans le thread initial. Pour en créer de nouveaux, elle instancie java.lang.Thread
,
puis lance le thread au moyen de la méthode start
.java.lang.Thread
(et java.lang.Object
)
ainsi que le tutoriel sur la programmation concurrente.Première façon de faire: dériver java.lang.Thread
.
public classe MonPremierThread extends Thread {
public void run() { ... }
}
L'appel à la méthode start
(héritée) lance l'exécution de la méthode run
.
L'exécution du thread se termine au retour de la méthode run
.
MonPremierThread t = new MonPremierThread();
t.start();
L'héritage de java.lang.Thread
est contraignant car il empêche tout autre héritage.
Il existe une seconde façon de faire: implémenter l'interface java.lang.Runnable
.
public classe MaClasseRunnable implements Runnable {
public void run() { ... }
}
On passe une instance de la classe implémentant Runnable
au constructeur de Thread
.
MaClasseRunnable r = new MaClasseRunnable();
Thread t = new Thread( r );
t.start();
Tous les threads ont accès au même espace mémoire. Quand les threads manipulent une référence vers un même objet, les accès concurrents au même objet sont suceptibles de génèrer des erreurs.
Pour les éviter, chaque objet a un verrou (intrinsic lock, monitor lock, monitor). Un thread qui veut un accès exclusif à un objet acquière ce verrou, puis le libère quand il a finit. Entre temps, il possède le verrou. Aucun autre thread ne peut alors acquérir le verrou de cet objet.
synchronized
§Quand un thread appelle une méthode synchronized
d'un objet, il acquière son verrou
et le relâche à la fin de l'exécution de la méthode.
synchronized typeRetour nomMethode(listeParametres) { ... }
On peut aussi écrire des blocs synchronized
pour avoir un niveau plus fin
et éviter un blocage non nécessaire.
synchronized (this) {
...
}
EvenCounter
. Que fait-elle ?EvenCounterTest
dans laquelle vous instanciez un seul objet de la classe
EvenCounter
, que vous exécutez dans deux threads.synchronized
à la méthode toNextEven
. Que se passe-t-il ?Modifiez les classes EvenCounter
et EvenCounterTest
de façon à ce que
la valeur du compteur ne s'affiche que tant qu'elle est inférieure à 50.
Utilisez le mot-clé synchronized
a bon escient pour éviter les accès concurrents,
tout en permettant aux deux threads de travailler.
Astuce: Préfixez les affichages par Thread.currentThread().getName()
.
Quand plusieurs threads partagent des données, il peut y avoir interférence (deux exécutions d'une même méthode sont entrelacées) ou incohérence (les appels de différentes méthodes d'un même objet sont entrelacés).
Pour éviter ces problèmes, on utilise le mot-clé synchronized
.
Quand un thread appelle une méthode synchronized
d'un objet ou exécute
un bloc synchronized(this)
dans une de ses méthodes, il acquière son verrou,
et le relâche à la fin de l'exécution.
start
.run
.yield
.java.lang.Thread
§
start
: active ce thread.run
: exécute ce thread.interrupt
: interromp ce thread.join
: attend que ce thread se termine.sleep
: endort ce thread durant un certain temps (en millisecondes).yield
: ce thread rends la main.
Modifiez la classe EvenCounterTest
de façon à faire afficher par le thread principal
un message de fin sur la sortie standard.
Il peut y avoir plusieurs problèmes de concurrence:
- deadblock : chaque thread laisse passer l'autre (image de deux personnes qui n'avancent pas tant que l'une n'a pas fait le premier pas).
- liveblock : chaque thread réagit par rapport à l'autre (image de deux personnes qui ne parviennent pas à se croiser en faisant toutes deux un pas de même côté)
- starvation : un thread lent empêche les autres de faire leur travail.
Pour coordonner les threads, on implémente des commandes bloquantes avec les méthodes wait
et notifiy(All)
de java.lang.Object
.
java.lang.Object
§
wait
: le thread courant doit posséder le verrou de l'objet (c'est pourquoi la méthode dans laquellewait
est appelée doit être déclaréesynchronized
). Il relâche le verrou et attend qu'un autre thread le réveille parnotify(All)
(ou qu'une durée donnée soit écoulée). Il attend ensuite d'obtenir le verrou pour poursuivre l'exécution.notifyAll
: réveille tous les threads en attente sur l'objet.notify
: réveille un seul thread, choisi arbitrairement.
archive
.ProducerConsumerTest
? Compilez et exécutez. Que se passe-t-il ?SyncCubbyHole
, qui étend CubbyHole
et qui redéfinit les méthodes
get
et put
en les marquant synchronized
et en appelant les méthodes wait
et notifyAll
.NB: Une bonne pratique est d'appeler wait
dans une boucle testant la condition attendue
(myProduct == null
ou myProduct != null
), car le thread qui attend peut être réveillé par un
thread quelconque alors que la condition attendue n'est pas vérifiée.
join
afin d'exploiter le résultat de leurs traitements.wait
et notifyAll
pour implémenter des commandes bloquantes:
- Tous les objets peuvent mettre en attente le thread courant avec
wait
.- Tous les objets peuvent réveiller le(s) thread(s) bloqué(s) par eux, avec
notify(All)
.
Piscine
et
Baigneur
. Que font-elles ?BaigneursTest
qui lance des threads opérant sur 150 instances de
la classe Baigneur
, chacune connaissant un seul objet de type Piscine
:Piscine piscine = new Piscine(); //la piscine
int n = 150;
Thread[] baigneurs = new Thread[n];
for (int i = 0; i < n; i++) //les baigneurs
baigneurs[i] = new Thread( new Baigneur(piscine, 5) );
Piscine
, utilisez à bon escient le mot-clé synchronized
.Le package java.util.concurrent
contient une classe Executors
fabriquant:
newSingleThreadExecutor()
newFixedThreadPool()
Ces méthodes renvoient en fait un objet de type ExecutorService
, sous-type de Executor
.
Autrement dit, un objet issu d'une classe implémentant l'interface ExecutorService
,
dérivant l'interface Executor
.
Les objets de type Executor
possèdent une méthode execute()
qui crée,
puis démarre un thread.
Si e
est un objet de type Executor
et si r
est un objet de type Runnable
,
alors ces codes sont équivalents:
Thread t = new Thread(r);
t.start();
e.execute(r);
BaigneursTest2
qui, au lieu de manipuler un tableau de threads
comme dans BaigneursTest
, utilise le pool de threads renvoyé par la méthode
newFixedThreadPool()
de Executors
.shutdown()
pour finir l'exécution des threads et ne plus attendre
de nouvelles tâches.