Blue Flower

Chercher

1. Thread et processus

1.1. Processus et threads en Java

Tout programme java  s'exécute dans un  processus  en l'occurrence la JVM. Au démarrage de toute application java, l'environnement de java lance le processus principal de l'application.
Dans sa conception, le langage java intègre la notion de  thread qui est, par définition, un processus léger.
Dans une application java, deux objets peuvent interagir quand l'un invoque une méthode de l'autre objet; parfois cela se fait de façon concurrente. A partir de Java 1.5, l'API "java.util.concurrent" traite les problématiques de programmes concurrents donc des threads. Cette API est construit sur les concepts de programmation multithreadée. Les classes et les interfaces de cet API ne modifient pas la notion de thread mais l'enrichissent et étendent les fonctionnalités de l'interface Runnable et de la classe Thread.

1.2. Processus

Un processus dans un système UNIX / Linux est un ensemble de ressources telles que, le segment de code, le segment de données de l'utilisateur, le segment de données du système, le répertoire de travail, le descripteurs de fichiers, l’identificateur de l’utilisateur ayant lancé le processus, l'identificateur du groupe dont est issu le processus, les informations sur l’emplacement des données en mémoire. Sous UNIX comme sous les autres systèmes d'exploitation,  un processus  ce sont donc des informations nécessaires à l’ordonnanceur, aux valeurs des registres, au compteur d’instructions, à la pile d’exécution et aux informations relatives aux signaux.

illustration thread
thread

1.3. Thread

Un "thread" ou "processus léger" est une unité d’exécution autonome qui peut effectuer des tâches en parallèle avec d'autres threads; il est constitué d'un identificateur, d'un compteur de programmes, d'une pile et d'un ensemble de variables locales. Le flot de contrôle d'un thread est  purement séquentiel.
Plusieurs threads peuvent être associés à un "processus lourd" (qui possède donc plusieurs flot de contrôle séquentiels, ou parallèles). Tous les threads associés à un processus lourd ont en commun un certain nombre de ressources, telles que: une partie du code à exécuter, une partie des données, des fichiers ouverts et des signaux.
Quand on parle de thread il faut retenir  l’aspect "unité d’exécution". Contrairement aux processus, les threads au sein d'un processus partagent le même espace d’adressage. Tous ces aspects font que la communication inter-thread occasionne peu de surcharge et le passage contextuel (context switching) d’une thread à une autre est peu coûteux en temps CPU et en consommation de mémoire. Toutefois, si on ne fait pas très attention lors de l'utilisation des threads, des difficultés énormes peuvent surgir notamment lors de l'accès concurrent aux zones critiques d'un programme.

1.4. Gestion des threads

Pour éviter tous les problèmes relatifs à l'accès concurrent aux zones critiques, plusieurs mécanismes de synchronisation sont disponibles:

  • Les sémaphores
  • Les moniteurs
  • Les techniques introduites via les packages de java.util.concurrent

 La partie la plus intrinsèque et évidente de la synchronisation est gérée par des techniques qui s'inspirent des moniteurs et des sémaphores; les packages de "java.util.concurrent" complètent et améliorent les premiers en introduisant des notions comme:

  • Lock objects (objet de verrouillage): les objets de verrouillage supportent les mécanismes de verrouillage et d' exclusion mutuelle permettant de simplifier beaucoup d'applications concurrentes.
  • Executors : cet API de haut niveau permet de lancer et de gérer les threads. L'implémentation de Executor au sein de "java.util.concurrent" fournit une gestion de pool de threads pour toute sorte d'application Java.
  • Concurrent collections: les fonctionnalités de cet API rendent facile le management de larges collections de données et permettent ainsi de réduire le besoin de synchronisation.
  • Atomic variables : Le paquet java.util.concurrent.atomic définit les classes qui prennent en charge des opérations atomiques sur des variables simples; les variables  atomiques ont des fonctionnalités qui facilitent la synchronisation et qui permettent d'éviter les erreurs d'accès en écriture en mémoire.
  • ThreadLocalRandom (depuis la JDK 7): cette classe  permet de fournir une génération efficiente de nombre aléatoire pour gérer les threads dans des applications Java qui en ont besoin.

2 Création de threads

2.1. Généralité

En  Java on a deux façons pour créer un thread; soit on implémente l'interface Runnable du package "java.lang.Runnable", soit on étend la classe Thread du package "java.lang.Thread".
L'interface Runnable possède une seule méthode  appelée run().  La classe Thread, dans sa conception, implémente l'interface Runnable; elle possède d'autres méthodes en plus de la  méthode run(). C'est dans la  méthode run() qu'on l'on implémént le code qui sera exécuté par le thread; cette méthode run()  est appelée par la méthode start() de la classe Thread pour lancer l'exécution du thread. Quand la méthode "start()" lance l'exécution du thread, elle rend la main tout de suite à la fonction au sein de laquelle on l'a utilisée c'est à dire qu'elle n'attend pas la fin de la méthode "run()" pour passer la main. A partir de ce moment le Thread s'exécute parallèlement aux autres threads présents au sein de la JVM.
Un thread vit jusqu’à la fin de l’exécution de sa méthode run(). Lancer un thread signifie demander à la JVM d'exécuter cette classe dans un fil d'exécution particulier au sein d'un processus. Par défaut, un thread est créé dans le groupe courant (c'est à dire celui qui l'a créé). Au démarrage, un thread est créé dans le groupe de nom main. On peut créer d'autres groupes et créer des threads appartenant à ces groupes en se servant de la classe ThreadGroup comme suit:
ThreadGroup groupe = new ThreadGroup("Mon groupe");
Thread processus = new Thread(groupe, "Un processus");

...

2.2. Création de thread par dérivation de la classe Thread

Exemple
Dans cet exemple je crée une application qui affiche juste deux messages; un message via la méthode run() du thread et l'autre via la thread principal main() de l'application.

public class Principale {
private static class Thread1 extends Thread { public void run() { System.out.println("Je suis un Thread JAVA"); } } public static void main(String args[]) { Thread1 th = new Thread1(); th.start(); System.out.println("Je suis dans la fonction principale"); } }

 On crée  un thread appelé Thread1 par  dérivation de la classe java Thread; au sein  de cette sous-classe Thread1 on redéfinit la méthode run(); cette  méthode  sert juste à afficher un message. A l'intérieur de la classe "Principal" on va se servir d'une instance de la classe "Thread1" en déclarant la variable "th" de type Thread1, et pour lancer notre thread on  fait appel à la méthode start(). 

A l'exécution du programme on a à la sortie:
Je suis dans la fonction principale <--- affichage issu du main
Je suis un thread JAVA <--- affichage issu de l'exécution de run() i.e du thread

Ici on a aucun mécanisme qui permet de gérer les threads (main et Thread1; c'est la JVM qui s'en occupe)

2.3. Création de thread par implémentation de l'interface Runnable

L'interface  Runnable permet  l’utilisation  des  threads  dans  la Java  machine; Pour être réellement exécuté, un Runnable  doit être passé en paramètre à un Thread ou un ExecutorService (à voir dans la suite).

public class Principale {
private static class Thread2 implements Runnable { public void run() { System.out.println("Je suis un Thread JAVA"); } } public static void main(String args[]) { Thread2 th2 = new Thread2(); Thread th = new Thread (th2); th.start(); System.out.println("Je suis dans la fonction principale"); } }

A l’exécution du programme on a à la sortie comme ci-dessus:
Je suis dans la fonction principale <--- affichage issu du main
Je suis un thread JAVA <--- affichage issu de l'exécution de th2 i.e du thread

Pareil que ci-dessus au niveau de l'affichage ; on a aucun mécanisme qui permet de gérer les threads (main et Thread2) c'est la JVM qui s'en occupe. Comment donc gérer et contrôler les threads dans un programme écrit en java ?

Certaines méthodes de la Thread permettent de gérer  l'exécution des thread.

2.4. Les méthodes de la classe Thread

Les méthodes statiques:

Les méthodes statiques, appelées aussi méthodes de classe agissent sur le Thread appelant;

  • Thread.sleep( lon g ms ) : bloque le thread appelant pour la durée spécifiée;
  • Thread.yield() : Le thread appelant relâche le processeur  au profit d’un thread de même priorité; 
  • Thread.currentThread() : retourne une référence sur le thread appelant;

Les méthodes d'instance:
Ces méthodes sont  les méthodes qui peuvent agir sur une instance quelconque de la classe;

MonThread p = new Monthread();

  • p.start() : démarre le thread p;
  • p.isAlive(): détermine si p est vivant ou terminé
  • p.join(): bloque l'appelant jusqu'à ce que p soit terminé
  • p.setDaemon(): attache l' attribut "Deamon" à p
  • p.setPriority(): assigne la priorité "pr"  à p
  • p.getPriority(): retourne la priorité de p;
  • ...

3. Quelques exemples

3.1. Premier exemple

 /******** Parallel:  classe regroupant des méthodes utiles **********/
public class Parallel {
    void println( String s) { 
System.out.println(s);
} void println( int i) {
System.out.println(i);
} void tab( int n) { for (int i = 1; i <= n ; i++)
System.out.println(" "); } static void Zzz( ) {
sleep( negexp(0.1) ) ;
} static void Zzz(int n) {
sleep( negexp(n*0.1) ) ;
} static void sleep(double sec) { try {
Thread.sleep((long) (1000 * sec));
} catch (InterruptedException e) {} } static float negexp( double a ) {
return (float) ( - Math.log(Math.random( )) * a );
} static int randint( int a, int b ) {
return (int) ( a + (b-a) * Math.random( ) );
} static boolean draw( double prob ){
return Math.log(Math.random( )) <= prob ; } } /**************** Producteur/Consommateurs ************/ public class ProdCons extends Parallel{ static final int SIZE = 3 ; static int [] T = new int[SIZE]; static int in = 0, out = 0; // ================ les Threads ================ class Producteur extends Thread { public void run( ) {
int tmp;
Zzz( ); for (int i=1; i<=6; i++) { System.out.println( i + " ==>"); tmp = in; T[tmp] = i; in = (tmp+1) % SIZE; Zzz( ); } } } class Consommateur extends Thread{ public void run( ) {
int tmp;
Zzz( ); for (int i=1; i<=6; i++) {
tmp = out; System.out.println(" ==> " + T[tmp] ); Zzz( ); out = (tmp+1) % SIZE; } } } // ====== programme principal ================ public static void main(String args[]) {
new ProdCons( ).go( );
} void go ( ) { System.out.println( " >> Main démarre......." ); new Producteur( ).start( ); new Consommateur( ).start( ); sleep( 5 ); System.out.println( " >> Main termine"); } }

précédent