Parallélisme en Java

François Sarradin

1. Création d'un thread

Java offre deux façons de créer des thread :

La classe Thread déclare une méthode abstraite (une méthode abstraite est une méthode sans implémentation (ou sans corps). L'implémentation est donnée en écrivant le corps de la méthode dans une sous-classe) run() dans laquelle doit être rédigé le programme du thread. Dans l'exemple qui suit, nous créons deux processus qui vont tour à tour afficher des chaînes de caractères :

class A extends Thread
{
    public void run ()
    {
        for (int i = 0; i < 8; i++)
        {
            System.out.print ("A" + i + " ");
            try sleep (100);
            catch (InterruptedException e) {}
        }
    }
}

class B extends Thread
{
    public void run ()
    {
        for (int i = 0; i < 8; i++)
        {
            System.out.print ("B" + i + " ");
            try sleep (200);
            catch (InterruptedException e) {}
        }
    }
}

public class TestThread1
{
    static public void main (String args[])
    {
        new A ().start ();
        new B ().start ();
    }
}

Dans l'exemple, la vitesse d'exécution des threads est modifiée au moyen de la méthode sleep(int t) qui immobilise un processus durant t millisecondes. De cette modification, il résulte un entrelacement des impressions exécutées par les threads issus des classes A et B. Par exemple :

A0 B0 A1 B1 A2 A3 B2 A4 A5 B3 A6 A7 B4 B5 B6 B7

Nous pouvons remarquer que les classes A et B ont une écriture très proche. La programmation objet va permettre de factoriser ces deux écritures en une seule et pour un résultat équivalent :

class PrintingThread extends Thread
{
    protected String name;
    protected int periode;

    public PrintingThread (String name, int periode)
    {
        this.name = name;
        this.periode = periode;
    }

    public void run ()
    {
        for (int i = 0; i < 8; i++)
        {
            System.out.print (name + i + " ");
            try sleep (periode);
            catch (InterruptedException e) {}
        }
    }
}

public class TestThread2
{
    static public void main (String args[])
    {
        new PrintingThread ().start ("A", 100);
        new PrintingThread ().start ("B", 200);
    }
}

2. Mécanismes de synchronisation en Java

Dans la section précédente, nous avons vu qu'il était possible de demander au système de réaliser plusieurs traitement en même temps. Toutefois, il existe un inconvénient par rapport à cette capacité. Supposons que deux threads veulent accèder à la même imprimante pour y envoyer chacun un fichier. Si ces threads accèdent en même temps à cette imprimante, le résultat sera totalement illisible puisque certaines lignes, certains mots ou certains caractères des deux fichiers risquent de se mélanger avec ceux de l'autre fichier. Une stratégie plus sure consisterait à donner la priorité à l'un des deux threads et à mettre l'autre en attente de la fin d'impression. Cette technique qui n'autorise qu'un nombre limité de thread pour une ressource donnée permet d'assurer l'exclusion mutuelle pour cette ressource.

Java offre la primitive synchronized pour assurer l'exclusion mutuelle sur des objets par l'intermédiaire de leurs méthodes. Cependant, elle n'est pas suffisantes pour résoudre les principaux problèmes de la synchronisation de thread. Pour palier ce manque, nous devons la primitive de synchronisation pour élaborer des structures plus robustes comme les sémaphores et les moniteurs à la Hoare.

2.1. Exemple des sémaphores

/*
 * Semaphore.java - implémentation des sémaphores en Java
 *
 */

public class Semaphore
{
    private int counter;

    public Semaphore () { counter = 0; }
    
    public Semaphore (int i)
    {
        if (i < 0)
            throw new IllegalArgumentException (i + " < 0");
        counter = i;
    }

    public synchronized void v ()
    {
        if (counter == 0)
            this.notify ();
        counter++;
    }

    public synchronized void p ()
    {
        while (counter == 0)
            this.wait ();
        counter--;
    }
}

Exercices

  1. Soient deux classes Java Ping et Pong affichant respectivement une série de Ping et une série de Pong. Créez la classe PingPong qui utilise ces deux classes et la classe Semaphore pour afficher alternativement Ping et Pong.
  2. Implémentez le problème du producteur/consommateur en Java. Pour cela, vous devrez créer trois classes : une classe dérivée de l'interface Ressource qui représente une ressource avec les méthodes produire() et consommer() protégées par des sémaphores, une classe Producteur et une classe Consommateur. L'application sera représentée par la classe AppProdCons qui devra créer une ressource, un producteur et un consommateur, lier les deux derniers objets à la ressource et lancer l'exécution parallèle.
    public interface Ressource
    {
        public void produire  (int qtt);
        public void consommer (int qtt);
    }
    

Copyright © 2002-2005 - François Sarradin