Aller au contenu

Classes Runnable abstraites pour les événements de threads

Jusqu'à présent, nous avons vu comment créer des threads simples qui exécutent du code une seule fois. Cependant, de nombreuses applications ont besoin de threads qui répondent à des événements à intervalles réguliers ou en continu. Une approche est de créer une classe Runnable abstraite qui encapsule la logique de gestion des événements.

Concept général

L'idée est de créer une classe abstraite qui implémente Runnable et qui offre une méthode abstraite que les sous-classes doivent implémenter.

Nouveau mot clé - volatile

Le mot clé volatile sert à indiquer que cette variable pourrait être lue par plusieurs threads simultanément. Ce mot clé s'assure que la lecture de cette variable sera la bonne. Cependant, il n'assure pas l'intégrité de la modification de la variable.

Voici un exemple avec un timer qui se déclenche à intervalles réguliers :

public abstract class RunnableTimer implements Runnable {

    public abstract void tick();

    private int delay = 1000;
    private volatile boolean doitArreter = false;

    public RunnableTimer() {}

    public RunnableTimer(int delay) {
        this.delay = delay;
    }

    public void arreter() {
        this.doitArreter = true;
    }

    @Override
    public void run() {
        while (!doitArreter) {
            try {
                Thread.sleep(delay);
                this.tick();
            } catch (InterruptedException e) {}
        }
    }
}

On pourrait ensuite utiliser la classe comme suit :

public class App {
    static void main() {

        RunnableTimer timer = new RunnableTimer() {
            @Override
            public void tick() {
                System.out.println("Thread: Tick");
            }
        };
        Thread thread = new Thread(timer);
        thread.start();

        // Arreter
        IO.readln("** Appuyez sur ENTER pour arrêter **\n");
        timer.arreter();
        System.out.println("Timer arrêté");

    }
}

En définissant la méthode tick() dans l'application principale, on pourra décider ce qui se passe à chaque seconde.

Exemple avec une nouvelle classe

Créons une classe qui affiche un message toutes les 2 secondes :

public class MessageTimer extends RunnableTimer {

    private int compteur = 0;

    public MessageTimer() {
        super(2000);  // Appeler tick() toutes les 2 secondes
    }

    @Override
    public void tick() {
        compteur++;
        System.out.println("Tick #" + compteur);
    }
}

Ensuite, utilisons-la dans notre application :

public class App {
    public static void main(String[] args) {
        MessageTimer timer = new MessageTimer();
        Thread thread = new Thread(timer);
        thread.start();

        // Arrêter quand l'utilisateur appuie sur ENTER
        IO.readln("** Appuyez sur ENTER pour arrêter **\n");

        // Arrêter le timer
        timer.arreter();
        System.out.println("Timer arrêté");
    }
}

Résultat :

Tick #1
Tick #2
Tick #3
Timer arrêté
Tick #4

Points importants

Gestion de l'InterruptedException

Bien que nous n'affichions pas l'exception dans la méthode run(), il est possible que le thread soit interrompu en cas d'arrêt forcé. Une meilleure pratique serait de :

@Override
public void run() {
    while (!doitArreter) {
        try {
            Thread.sleep(delay);
            this.tick();
        } catch (InterruptedException e) {
            System.err.println("Timer interrompu");
            break;
        }
    }
}

Il pourrait ensuite être arrêté de l'intérieur via un méthode abstraite arreter() et Thread.currentThread().interrupt() ou de l'extérieur en utilisant monThread.interrupt().

Créer une application de discussion multithread

Nous allons procéder à créer une petite application de discussion multithread. Vous pouvez utiliser netcat comme client. Votre application doit avoir les fonctionnalités suivantes : 1. Permettre la connexion de plusieurs clients 1. Lorsqu'un client envoie un message, tous les utilisateurs devront recevoir ce message

Comment procéder?

  • Utilisez une classe Runnable abstraite avec les méthodes abstraites suivantes :
  • void messageEnvoye(String message);
  • void clientDeconnecte(Socket client);
  • Dans vos threads:
  • quand un message est reçu, l'envoyer à tous en appelant la méthode messageEnvoye()
  • quand un client déconnecte, informer le thread principal en appelant la méthode clientDeconnecte()
  • Le thread principal garde en mémoire tous les sockets des clients et les utilise pour faire un broadcast aux autres.

Exemple

Client1
Salut
BROADCAST: Salut
Ça va?
BROADCAST: Ça va?
BROADCAST: Oui, je vais bien!
Client2
BROADCAST: Salut
BROADCAST: Ça va?
BROADCAST: Oui, je vais bien!
Client3
BROADCAST: Salut
BROADCAST: Ça va?
Oui, je vais bien!
BROADCAST: Oui, je vais bien!

Défi supplémentaire

  • Faites en sorte que les BROADCAST ne soient pas reçus par celui qui a envoyé le message