Java Language Thread Interruption / Stopping Threads


Exemple

Chaque thread Java a un indicateur d'interruption, qui est initialement faux. Interrompre un thread, ce n’est rien d’autre que de lui attribuer la valeur true. Le code exécuté sur ce thread peut vérifier le drapeau à l'occasion et agir en conséquence. Le code peut également l'ignorer complètement. Mais pourquoi chaque fil aurait-il un tel drapeau? Après tout, avoir un drapeau booléen sur un fil est quelque chose que nous pouvons simplement organiser nous-mêmes, si et quand nous en avons besoin. Eh bien, il existe des méthodes qui se comportent de manière particulière lorsque le thread sur lequel elles s'exécutent est interrompu. Ces méthodes sont appelées méthodes de blocage. Ce sont des méthodes qui placent le thread dans l'état WAITING ou TIMED_WAITING. Lorsqu'un thread est dans cet état, l'interrompre provoquera une exception InterruptedException sur le thread interrompu, au lieu que l'indicateur d'interruption soit défini sur true et que le thread redevienne RUNNABLE. Le code qui appelle une méthode de blocage est obligé de gérer l'exception Interrupted, car il s'agit d'une exception vérifiée. Donc, et par conséquent son nom, une interruption peut avoir pour effet d'interrompre un WAIT, le terminant effectivement. Notez que toutes les méthodes en attente (par exemple, le blocage des E / S) ne réagissent pas de cette manière aux interruptions, car elles ne mettent pas le thread en attente. Enfin, un thread dont l’indicateur d’interruption est défini, qui entre dans une méthode de blocage (c’est-à-dire qui tente d’entrer dans un état en attente), lancera immédiatement une exception InterruptedException et l’indicateur d’interruption sera effacé.

Outre ces mécanismes, Java n’attribue aucune signification sémantique particulière à l’interruption. Le code est libre d'interpréter une interruption comme bon lui semble. Mais le plus souvent, l'interruption est utilisée pour signaler à un thread qu'il doit cesser de fonctionner dès que possible. Mais, comme il ressort clairement de ce qui précède, il appartient au code de ce thread de réagir de manière appropriée à cette interruption afin de ne plus fonctionner. Arrêter un thread est une collaboration. Lorsqu'un thread est interrompu, son code en cours peut avoir plusieurs niveaux dans la trace de la pile. La plupart du code n'appelle pas de méthode de blocage et se termine suffisamment rapidement pour ne pas retarder indûment l'arrêt du thread. Le code qui devrait principalement concerner la réactivité aux interruptions, est le code qui est en boucle et gère les tâches jusqu’à ce qu’il n’y en ait plus, ou jusqu’à ce qu’un indicateur soit défini pour l’interrompre. Les boucles qui traitent des tâches éventuellement infinies (c'est-à-dire qu'elles continuent à fonctionner en principe) devraient vérifier l'indicateur d'interruption afin de quitter la boucle. Pour les boucles finies, la sémantique peut exiger que toutes les tâches soient terminées avant de se terminer, ou il peut être approprié de laisser certaines tâches non gérées. Le code qui appelle les méthodes de blocage sera obligé de gérer l'exception Interrupted. S'il est sémantiquement possible, il peut simplement propager l'InterruptedException et déclarer le lancer. En tant que tel, il devient une méthode de blocage en ce qui concerne ses appelants. S'il ne peut pas propager l'exception, il doit au minimum définir l'indicateur interrompu, afin que les appelants plus haut dans la pile sachent également que le thread a été interrompu. Dans certains cas, la méthode doit continuer d'attendre quelle que soit l'interruption, auquel cas elle doit retarder la définition de l'indicateur interrompu jusqu'à la fin de l'attente, cela peut impliquer la définition d'une variable locale à vérifier avant de quitter la méthode. puis interrompre son fil.

Exemples :

Exemple de code qui arrête la gestion des tâches lors de l'interruption

class TaskHandler implements Runnable {
    
    private final BlockingQueue<Task> queue;

    TaskHandler(BlockingQueue<Task> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        while (!Thread.currentThread().isInterrupted()) { // check for interrupt flag, exit loop when interrupted
            try {
                Task task = queue.take(); // blocking call, responsive to interruption
                handle(task);
            } catch (InterruptedException e) {
                Thread.currentThread().interrupt(); // cannot throw InterruptedException (due to Runnable interface restriction) so indicating interruption by setting the flag
            }
        }
    }
    
    private void handle(Task task) {
        // actual handling
    }
}

Exemple de code qui retarde la définition de l'indicateur d'interruption jusqu'à sa complète exécution:

class MustFinishHandler implements Runnable {

    private final BlockingQueue<Task> queue;

    MustFinishHandler(BlockingQueue<Task> queue) {
        this.queue = queue;
    }

    @Override
    public void run() {
        boolean shouldInterrupt = false;
        
        while (true) {
            try {
                Task task = queue.take();
                if (task.isEndOfTasks()) {
                    if (shouldInterrupt) {
                        Thread.currentThread().interrupt();
                    }
                    return;
                }
                handle(task);
            } catch (InterruptedException e) {
                shouldInterrupt = true; // must finish, remember to set interrupt flag when we're done
            }
        }
    }

    private void handle(Task task) {
        // actual handling
    }
}

Exemple de code qui a une liste de tâches fixe mais qui peut quitter tôt lorsqu'il est interrompu

class GetAsFarAsPossible implements Runnable {

    private final List<Task> tasks = new ArrayList<>();

    @Override
    public void run() {
        for (Task task : tasks) {
            if (Thread.currentThread().isInterrupted()) {
                return;
            }
            handle(task);
        }
    }

    private void handle(Task task) {
        // actual handling
    }
}