parallélisme en Java - partie2
La concurrence en java est un parallélisme de conception, appelé aussi pseudo-parallélisme, tandis que le parallélisme s’utilise dans les architectures multiprocesseurs, et les architectures distribuées.
Le langage Java possède des mécanismes permettant de gérer la concurrence, une tâche correspond à un contexte d’exécution (thread), c’est-à-dire à un flux de contrôle unique au sein d’un même programme.
1. Généralités :
Un thread, appelé aussi un processus léger, est un processus à l'intérieur d'un processus, les ressources allouées à un processus vont être partagées entre les threads qui le composent. Un processus possède au moins un thread qui exécute le programme principal, la fonction main().
Dans un système monoprocesseur sur lequel il y a plus d’un contexte exécutable, le partage du processeur entre les différents threads est réalisé par un composant de la machine virtuelle appelé l’ordonnanceur, Il effectue cycliquement les opérations suivantes:
· Sélectionner un contexte exécutable.
· Attribuer le processeur pour un temps définit.
2. Création d'un thread :
Les threads sont représentés par la classe Thread et l’interface Runnable, qui font partie du paquetage de classes java.lang. Ainsi, on n’a pas besoin de l’instruction import pour les utiliser.
Création d’une classe qui hérite de la classe Thread. Cette classe doit surcharger la méthode run() de la classe Thread. Ce dernier se termine lorsque sa méthode run() termine son exécution.
class MonThread extends Thread {
MonThread() {
... code du constructeur ...
}
public void run() {
... code à exécuter dans le thread ... }}
Une instance de Thread est lancée en appelant la méthode start() définie par la classe thread:
MonThread p = new MonThread();
p.start();
L'appel de la méthode start() passe le thread à l'état "prêt". C'est la machine virtuelle JAVA qui décide du moment auquel le thread va s'exécuter. Lorsque le thread démarre, la JVM appelle sa méthode run().
La classe Thread offre des méthodes qui permettent la gestion des threads tels que :
- sleep( long x ) : le thread qui appelle sleep(x) est bloqué pendant x millisecondes.
- isAlive() : retourne vrai si le thread auquel on applique la méthode est vivant, le thread vivant est donc prêt, bloqué ou en cours d'exécution.
- getPriority() et setPriority( int prio ) : permettent de manipuler la priorité du thread.
Implémentation de l'interface Runnable, cette interface déclare seulement la méthode run(). La classe Thread a un constructeur qui prend une instance implémentant Runnable en argument.
La classe se déclare alors en implémentant l’interface Runnable au lieu d'hériter de Thread:
class MonThread implements Runnable {
MonThread() {
... code du constructeur ...
}
public void run() {
... code à exécuter dans le thread ...
}
}
Pour créer et lancer un thread, on crée d'abord une instance de la classe, puis une instance de Thread sur laquelle on appelle la méthode start(), ainsi on a :
public static void main(String[] args) {
MonThread p = new MonThread();
Thread t = new Thread(p);
t.start();
}
4. Les verrous et la synchronisation des threads :
Il arrive fréquemment que plusieurs threads tentent de modifier simultanément l’état d’un objet, ceci peut conduire à un état d’incohérence.
Java résout ce problème grâce au concept de moniteur:
• Chaque objet est associé à un verrou pouvant être acquis ou relâché par les threads.
• Un bloc de code peut être contrôlé par le moniteur d’un objet v grâce à l’instruction synchronize :
synchronize(v)
{ ...
}
Lors de l’exécution de cette instruction, le thread :
o essaie d’acquérir le verrou associé à l’objet v. Cela n’est possible que si aucun autre thread n’a acquis ce verrou sans le relâcher.
o exécute les instructions du bloc.
o relâche le verrou.
Dans une application ou plusieurs threads s’échangent des données, il arrive qu’un thread doit suspendre son exécution jusqu’à ce qu’un autre thread ait effectué certaines opérations qui touchent ses données.
Pour cette raison, la classe Object fournit trois méthodes destinées à assurer la synchronisation entre plusieurs threads :
o wait(): Place le thread courant dans l’état non exécutable, et relâche le verrou associé à l’objet.
o notify(): Sélectionne un thread en attente sur l’objet courant et le rend à nouveau exécutable.
o notifyAll(): rend exécutable tous les threads en attente sur l’objet courant.
Les sémaphores : encapsulent un entier, et deux opérations atomiques d'incrémentation et de décrémentation. Ils permettent de restreindre le nombre de threads qui accèdent à un objet. L'appel à la méthode acquire() est bloquant si le nombre de thread maximum est atteint, et si la méthode release() n’a pas encore été appelée.