Historiquement, Java a essayé de fournir des interruptions préemptives limitées, mais il y a eu de nombreux problèmes, tels que les Thread.stop, Thread.suspend et Thread.resume abandonnés introduits plus tôt. D'autre part, par souci de robustesse du code d'application Java, le seuil de programmation est abaissé et la probabilité que des programmeurs ne connaissant pas le mécanisme sous-jacent endommagent involontairement le système est réduite.
Aujourd'hui, la planification des threads de Java ne fournit pas d'interruptions préemptives, mais utilise des interruptions coopératives. En fait, le principe de l'interruption coopérative est très simple : il s'agit d'interroger une certaine marque indiquant une interruption. Nous pouvons l'implémenter dans n'importe quel code ordinaire.
Par exemple le code suivant :
volatile bool isInterrupted ;
//…
pendant que(!isInterrupted) {
calculer();
}
Cependant, les problèmes de code ci-dessus sont également évidents. Lorsque le temps d’exécution du calcul est relativement long, il n’est pas possible de répondre à temps à l’interruption. D'un autre côté, en utilisant l'interrogation pour vérifier les variables d'indicateur, il n'existe aucun moyen d'interrompre les opérations de blocage de thread telles que l'attente et la mise en veille.
Si vous utilisez toujours l'idée ci-dessus, si vous souhaitez que l'interruption soit répondue en temps opportun, vous devez vérifier la variable mark via la planification des threads au bas de la machine virtuelle. Oui, cela se fait effectivement dans la JVM.
Ce qui suit est extrait du code source de java.lang.Thread :
public statique booléen interrompu() {
return currentThread().isInterrupted(true);
}
//…
booléen natif privé isInterrupted (boolean ClearInterrupted);
On peut constater que isInterrupted est déclaré comme méthode native, ce qui dépend de l'implémentation sous-jacente de la JVM.
En fait, la JVM maintient un indicateur d'interruption en interne pour chaque thread. Cependant, l'application ne peut pas accéder directement à cette variable d'interruption et doit fonctionner via les méthodes suivantes :
Discussion de classe publique {
//Définir la marque d'interruption
interruption publique vide() { ... }
//Obtenir la valeur de la marque d'interruption
public booléen isInterrupted() { ... }
//Efface la marque d'interruption et renvoie la valeur de la dernière marque d'interruption
public statique booléen interrompu() { ... }
}
Normalement, l'appel de la méthode d'interruption d'un thread ne provoque pas immédiatement une interruption, mais définit uniquement l'indicateur d'interruption à l'intérieur de la JVM. Par conséquent, en vérifiant l'indicateur d'interruption, l'application peut faire quelque chose de spécial ou ignorer complètement l'interruption.
Vous pensez peut-être que si la JVM ne fournit que ce mécanisme d'interruption grossier, elle n'a fondamentalement aucun avantage par rapport à la méthode propre à l'application de définition des variables d'interruption et d'interrogation.
Le principal avantage des variables d'interruption internes de la JVM est qu'elles fournissent un mécanisme permettant de simuler des « interruptions d'interruption » automatiques pour certaines situations.
Lors de l'exécution d'appels bloquants impliquant une planification de thread (comme attendre, dormir et rejoindre), si une interruption se produit, le thread bloqué lancera InterruptedException "aussi rapidement que possible". Par conséquent, nous pouvons utiliser le cadre de code suivant pour gérer les interruptions bloquant les threads :
essayer {
//attendre, dormir ou rejoindre
}
catch (InterruptedException e) {
//Quelques travaux de gestion des interruptions
}
Par "aussi vite que possible", je suppose que la JVM vérifie la variable d'interruption dans l'intervalle entre la planification des threads. La vitesse dépend de la mise en œuvre de la JVM et des performances du matériel.
Cependant, pour certaines opérations de blocage de threads, la JVM ne lève pas automatiquement InterruptedException. Par exemple, certaines opérations d'E/S et opérations de verrouillage interne. Pour ce type d'opération, les interruptions peuvent être simulées d'autres manières :
1) E/S de socket asynchrone dans java.io
Lors de la lecture et de l'écriture de sockets, les méthodes de lecture et d'écriture de InputStream et OutputStream se bloqueront et attendront, mais ne répondront pas aux interruptions Java. Cependant, après avoir appelé la méthode close du Socket, le thread bloqué lancera une SocketException.
2) E/S asynchrones implémentées à l'aide du sélecteur
Si le thread est bloqué dans Selector.select (dans java.nio.channels), l'appel de la méthode de réveil provoquera une exception ClosedSelectorException.
3) Acquisition du verrou
Si un thread attend d'acquérir un verrou interne, nous ne pouvons pas l'interrompre. Cependant, en utilisant la méthode lockInterruptably de la classe Lock, nous pouvons fournir des capacités d'interruption en attendant le verrou.
De plus, dans un cadre où les tâches et les threads sont séparés, une tâche ne sait généralement pas par quel thread elle sera appelée et ne connaît donc pas la stratégie du thread appelant pour gérer les interruptions. Par conséquent, une fois que la tâche a défini l'indicateur d'interruption de thread, il n'y a aucune garantie que la tâche sera annulée. Il existe donc deux principes de programmation :
1) Vous ne devez pas interrompre un thread à moins de connaître sa politique d'interruption.
Ce principe nous dit que nous ne devons pas appeler directement la méthode d'interruption d'un thread dans un framework tel qu'Executer, mais que nous devons utiliser des méthodes telles que Future.cancel pour annuler des tâches.
2) Le code de tâche ne doit pas deviner ce que l'interruption signifie pour le thread d'exécution.
Ce principe nous dit que lorsque le code général rencontre une InterruptedException, il ne doit pas l'attraper et "l'avaler", mais doit continuer à la renvoyer au code supérieur.
En bref, le mécanisme d'interruption non préemptive en Java nous oblige à modifier l'idée traditionnelle de l'interruption préemptive et à adopter les principes et modèles de programmation correspondants basés sur la compréhension de son essence.